~ new ForeignKey model no-hierarchy
authorEric Prud'hommeaux <eric@w3.org>
Sat, 12 Feb 2011 13:19:54 -0500
branchno-hierarchy
changeset 329 d23caa599848
parent 328 6343be6fac49
child 330 7f4830491c64
~ new ForeignKey model
- hierarchy special case
directmapping-test/src/main/scala/DirectMappingTestSuite.scala
directmapping/src/main/scala/DirectMapping.scala
rdb/src/main/scala/RDB.scala
sql/src/main/scala/SQL.scala
sql/src/test/scala/SQLTest.scala
--- a/directmapping-test/src/main/scala/DirectMappingTestSuite.scala	Sat Feb 12 11:41:00 2011 -0500
+++ b/directmapping-test/src/main/scala/DirectMappingTestSuite.scala	Sat Feb 12 13:19:54 2011 -0500
@@ -39,11 +39,6 @@
 }
 
 
-
-
-
-
-
 trait DirectMappingTest extends FunSuite with RDFModel with RDFImplicits with DirectMappingModule with TurtleModule {
 
   import DirectMapping._
--- a/directmapping/src/main/scala/DirectMapping.scala	Sat Feb 12 11:41:00 2011 -0500
+++ b/directmapping/src/main/scala/DirectMapping.scala	Sat Feb 12 13:19:54 2011 -0500
@@ -52,19 +52,15 @@
      * <http://www.w3.org/2001/sw/rdb2rdf/directGraph/>
      */
   
-    def references (t:Tuple, r:Relation):Set[ForeignKeyKey] = {
-      val allFKs:Set[ForeignKeyKey] = r.fks.keySet
-      val nulllist:Set[AttrName] = t.nullAttributes(r.header)
-      val nullFKs:Set[ForeignKeyKey] = allFKs filter { fk => (nulllist & fk.toSet) nonEmpty  }
-      r.fks.keySet -- nullFKs
+    def references (t:Tuple, r:Relation):Set[ForeignKey] = {
+      val nulls:Set[AttrName] = t.nullAttributes
+      val references = r.fks filter { case ForeignKey(as, _) => nulls & as.toSet isEmpty  }
+      references
     }
-  
+
     def scalars (t:Tuple, r:Relation):Set[AttrName] = {
-      val allAttrs:Set[AttrName] = r.header.keySet
-      val allFKs:Set[ForeignKeyKey] = r.fks.keySet
-      val unaryFKs:Set[AttrName] = allFKs map { _.attrs } filter { _.length == 1 } flatten
-
-      allAttrs -- unaryFKs
+      val notNulls:Set[AttrName] = t.notNullAttributes
+      notNulls filterNot { attrName => r.fks definesActuallyUnaryFK attrName }
     }
   
     /** The NodeMap-generating functions: */
@@ -81,7 +77,7 @@
         r.pk match {
           case Some(pk) =>
             /** Table has a primkary key. */
-            NodeIRI(nodemap(r.name, pk.attrs, t.lexvaluesNoNulls(pk.attrs)))
+            NodeIRI(nodemap(r.name, pk.attrs, t.notNullLexvalues(pk)))
           case None =>
             /** Table has no primkary key (but has some candidate keys). */
             NodeBNode(freshbnode())
@@ -108,7 +104,7 @@
         r.candidates.headOption match {
           // Known to have at least one key, so take the first one.
           case Some(firstKey) => {
-            val vs = t.lexvaluesNoNulls(firstKey.attrs)
+            val vs = t.notNullLexvalues(firstKey)
             nodes.ultimateReferent(r.name, firstKey, vs, db)
           }
           /** Table has no candidate keys. */
@@ -135,8 +131,8 @@
     }
   
     def directL (rn:RelName, s:Node, a:AttrName, h:Header, t:Tuple) : Option[Triple] = {
-      val p = predicatemap (rn, List(a))
-      t.lexvalue(a) match {
+      val p = predicatemap (rn, new AttrList { val attrs = List(a) } )
+      t(a) match {
 	case l:LexicalValue => {
 	  val o = literalmap(l, h.sqlDatatype(a))
 	  Some(Triple(SubjectNode(s),
@@ -147,19 +143,19 @@
       }
     }
 
-    def directN (s:Node, as:ForeignKeyKey, r:Relation, t:Tuple, nodes:NodeMap) : Triple = {
-      val p = predicatemap (r.name, as.attrs)
-      val ls:List[LexicalValue] = t.lexvaluesNoNulls(as.attrs)
-      val target = r.fks(as)
-      if (!nodes.contains(target.rel))
-        error("No referent relation \"" + target.rel + "\" to match " + r.name + t)
-      if (!nodes(target.rel).contains(target.key))
-        error("Relation " + target.rel + " has no attributes (" + target.key + ") to match " + r.name + t)
-      if (!nodes(target.rel)(target.key).contains(ls))
-        error("Relation " + target.rel + "(" + target.key + ") has no values " + ls + " to match " + r.name + t)
-      val o:Object = ObjectNode(nodes(target.rel)(target.key)(ls))
-      Triple(SubjectNode(s), PredicateIRI(p), o)
-    }
+    def directN (s:Node, fk:ForeignKey, r:Relation, t:Tuple, nodes:NodeMap) : Triple = {
+      val p = predicatemap (r.name, fk)
+      val ls:List[LexicalValue] = t.notNullLexvalues(fk)
+      val ForeignKey(as, Target(rel, key)) = fk
+      if (!nodes.contains(rel))
+        error("No referent relation \"" + rel + "\" to match " + r.name + t)
+      if (!nodes(rel).contains(key))
+        error("Relation " + rel + " has no attributes (" + key + ") to match " + r.name + t)
+      if (!nodes(rel)(key).contains(ls))
+        error("Relation " + rel + "(" + key + ") has no values " + ls + " to match " + r.name + t)
+      val o:Object = ObjectNode(nodes(rel)(key)(ls))
+       Triple(SubjectNode(s), PredicateIRI(p), o)
+     }
   
     // These implicits make nodemap and predicatemap functions prettier.
     implicit def relName2string (rn:RelName) = rn.n
@@ -170,8 +166,8 @@
       IRI(UE(rn) + "/" + pairs.mkString("_") + "#_")
     }
   
-    def predicatemap (rn:RelName, as:List[AttrName]) : IRI =
-      IRI(UE(rn) + "#" + as.mkString("_"))
+    def predicatemap (rn:RelName, as:AttrList) : IRI =
+      IRI(UE(rn) + "#" + as.attrs.mkString("_"))
 
     // TODO: aren't they already part of the RDF model?
     def XSD (d:Datatype) : IRI =
--- a/rdb/src/main/scala/RDB.scala	Sat Feb 12 11:41:00 2011 -0500
+++ b/rdb/src/main/scala/RDB.scala	Sat Feb 12 13:19:54 2011 -0500
@@ -30,27 +30,34 @@
       Header(s map { case (name, datatype) => (AttrName(name), datatype) } toMap)
   }
 
-  type AttrList = List[AttrName]
-  case class ForeignKeyKey (attrs:AttrList) {
+  trait AttrList {
+    val attrs:List[AttrName]
+    def isUnary:Boolean = attrs.length == 1
+  }
+
+  case class ForeignKey (attrs:List[AttrName], target:Target) extends AttrList {
     def toSet = attrs.toSet
   }
 
-  case class CandidateKey (attrs:AttrList)
+  case class CandidateKey (attrs:List[AttrName]) extends AttrList
   object CandidateKey {
     def apply (l:String*):CandidateKey =
       CandidateKey(l.toList map { AttrName(_) })
   }
   implicit def cc2list (cc:CandidateKey) = cc.attrs
 
-  case class ForeignKeys (m:Map[ForeignKeyKey, Target]) {
-    def apply (l:ForeignKeyKey) = m(l)
-    def keySet = m.keySet.toSet
-    def contains (l:AttrList) = m contains ForeignKeyKey(l) // self-promoting cheat
-    def refdAttrs (kk:ForeignKeyKey) = m(kk).key.attrs
+  case class ForeignKeys (fks:Set[ForeignKey]) {
+    def has(as:AttrList) = fks exists { _.attrs == as }
+    def getFK(as:AttrList):ForeignKey = fks find { _.attrs == as } get
+    def unaryFKs:Set[ForeignKey] = fks filter { _.isUnary }
+    def definesActuallyUnaryFK(a:AttrName):Boolean = unaryFKs exists { _.attrs contains a }
+    def filter(p: ForeignKey => Boolean):Set[ForeignKey] = fks filter p
+    def -(as:AttrList):ForeignKeys = ForeignKeys(fks filter { _.attrs == as })
   }
+
   object ForeignKeys {
-    def apply (s:(List[String], Target)*):ForeignKeys =
-      ForeignKeys(s map { case (keys, target) => (ForeignKeyKey(keys map { AttrName(_) }), target)} toMap)
+    def apply (fks:(List[String], Target)*):ForeignKeys =
+      ForeignKeys(fks map { case (keys, target) => ForeignKey(keys map { AttrName(_) }, target)} toSet)
   }
 
   case class Target (rel:RelName, key:CandidateKey)
@@ -71,11 +78,25 @@
     val DATETIME = Datatype("Datetime")
   }
 
+  // case class Tuple (m:Map[AttrName, CellValue]) {
+  //   def apply (a:AttrName) = m(a)
+  //   def lexvalue (a:AttrName) : CellValue = m(a)
+  //   def lexvaluesNoNulls (as:List[AttrName]) = as map { m(_).asInstanceOf[LexicalValue] }
+  //   def nullAttributes (h:Header) : Set[AttrName] = h.keySet filter { m(_) == ␀ }
+  // }
   case class Tuple (m:Map[AttrName, CellValue]) {
     def apply (a:AttrName) = m(a)
-    def lexvalue (a:AttrName) : CellValue = m(a)
-    def lexvaluesNoNulls (as:List[AttrName]) = as map { m(_).asInstanceOf[LexicalValue] }
-    def nullAttributes (h:Header) : Set[AttrName] = h.keySet filter { m(_) == ␀ }
+    // assumes that the AttrName does not correspond to null values
+    // for example, it's ok to call it with PK attributes
+    def notNullLexvalues (as:AttrList):List[LexicalValue] =
+      as.attrs map {
+        m(_) match {
+          case lexicalValue @ LexicalValue(_) => lexicalValue
+          case ␀ => error("this value MUST not be null")
+        }
+      }
+    def nullAttributes : Set[AttrName] = m collect { case (attrName, cellValue) if cellValue == ␀ => attrName } toSet
+    def notNullAttributes : Set[AttrName] = m collect { case (attrName, cellValue) if cellValue != ␀ => attrName } toSet
   }
   object Tuple {
     def apply (s:(String, CellValue)*):Tuple =
--- a/sql/src/main/scala/SQL.scala	Sat Feb 12 11:41:00 2011 -0500
+++ b/sql/src/main/scala/SQL.scala	Sat Feb 12 13:19:54 2011 -0500
@@ -168,7 +168,7 @@
 sealed abstract class KeyDeclaration extends FieldDescOrKeyDeclaration
 case class PrimaryKeyDeclaration(key:RDB.CandidateKey) extends KeyDeclaration
 case class CandidateKeyDeclaration(key:RDB.CandidateKey) extends KeyDeclaration
-case class ForeignKeyDeclaration(fk:RDB.ForeignKeyKey, rel:RDB.RelName, pk:RDB.CandidateKey) extends KeyDeclaration
+case class ForeignKeyDeclaration(fk:List[RDB.AttrName], rel:RDB.RelName, pk:RDB.CandidateKey) extends KeyDeclaration
 case class View(rel:RDB.RelName, defn:SelectORUnion) { // sibling of RDB.Relation
   override def toString = "CREATE VIEW " + rel + " AS\n" + defn
 }
@@ -213,14 +213,13 @@
       val pk0:Option[RDB.CandidateKey] = None
       val attrs0 = Map[RDB.AttrName, RDB.Datatype]()
       val candidates0 = List[RDB.CandidateKey]()
-      val fks0 = Map[RDB.ForeignKeyKey, RDB.Target]()
+      val fks0 = Set[RDB.ForeignKey]()
       /* <pk>: (most recently parsed) PRIMARY KEY
        * <attrs>: map of attribute to type (e.g. INTEGER)
        * <fks>: map holding FOREIGN KEY relation REFERENCES attr
        */
       val (pk, attrs, candidates, fks) =
-	reldesc.foldLeft((pk0, attrs0, candidates0, fks0))((p, rd) => {
-	  val (pkopt, attrs, candidates, fks) = p
+	reldesc.foldLeft((pk0, attrs0, candidates0, fks0)) { case ((pkopt, attrs, candidates, fks), rd) => {
 	  rd match {
 	    case FieldDesc(attr, value, pkness) => {
 	      val (pkNew, candNew) =
@@ -230,14 +229,14 @@
 	    }
 	    case PrimaryKeyDeclaration(key) =>
 	      // @@ why doesn't [[ candidates + RDB.CandidateKey(attr.n) ]] work?
-	      (Some(key), attrs, candidates ++ List(RDB.CandidateKey(key map {attr => RDB.AttrName(attr.n)})), fks)
+	      (Some(key), attrs, candidates :+ key, fks)
 	    case CandidateKeyDeclaration(key) =>
 	      // @@ why doesn't [[ candidates + RDB.CandidateKey(attr.n) ]] work?
-	      (pkopt, attrs, candidates ++ List(RDB.CandidateKey(key map {attr => RDB.AttrName(attr.n)})), fks)
+	      (pkopt, attrs, candidates :+ key, fks)
 	    case ForeignKeyDeclaration(fk, rel, pk) =>
-	      (pkopt, attrs, candidates, fks + (fk -> RDB.Target(rel, pk)))
+	      (pkopt, attrs, candidates, fks + RDB.ForeignKey(fk, RDB.Target(rel, pk)))
 	  }
-	})
+	} }
       val rd = RDB.Relation(relation, RDB.Header(attrs), List(), candidates, pk, RDB.ForeignKeys(fks))
       Create(relation, rd)
     }
@@ -262,8 +261,7 @@
     | "(?i)UNIQUE".r ~ "(" ~ rep1sep(attribute, ",") ~ ")" ^^
       { case _~"("~attributes~")" => CandidateKeyDeclaration(RDB.CandidateKey(attributes)) }
     | "(?i)FOREIGN".r ~ "(?i)KEY".r ~ "(" ~ rep1sep(attribute, ",") ~ ")" ~ "(?i)REFERENCES".r ~ relation ~ "(" ~ rep1sep(attribute, ",") ~ ")" ^^
-      { case _~_~"("~fk~")"~_~relation~"("~pk~")" => ForeignKeyDeclaration(RDB.ForeignKeyKey(fk), relation, RDB.CandidateKey(pk)) }
-  )
+      { case _~_~"("~fk~")"~_~relation~"("~pk~")" => ForeignKeyDeclaration(fk, relation, RDB.CandidateKey(pk)) }  )
 
   def typpe:Parser[RDB.Datatype] = (
       "(?i)INTEGER".r ~ opt(size)^^ { case _ => RDB.Datatype.INTEGER }
--- a/sql/src/test/scala/SQLTest.scala	Sat Feb 12 11:41:00 2011 -0500
+++ b/sql/src/test/scala/SQLTest.scala	Sat Feb 12 13:19:54 2011 -0500
@@ -792,7 +792,6 @@
                               FOREIGN KEY (worker) REFERENCES People(ID),
                               project STRING, PRIMARY KEY (worker, project), 
                               deptName STRING, deptCity STRING,
-                              FOREIGN KEY (worker) REFERENCES People(ID),
                               FOREIGN KEY (project, deptName, deptCity) REFERENCES Projects(name, deptName, deptCity),
                               FOREIGN KEY (deptName, deptCity) REFERENCES Department(name, city));
 INSERT INTO TaskAssignments (worker, project, deptName, deptCity) VALUES (7, "pencil survey", "accounting", "Cambridge");