~ made primaryKey an Option
authorEric Prud'hommeaux <eric@w3.org>
Mon, 27 Sep 2010 17:46:02 -0400
changeset 24 31cf43fcd94b
parent 23 40b955a81d55
child 25 f460e59ebcc2
~ made primaryKey an Option
+ test:2 People 1 Addresses 1 Department 2 Projects 1 Task
src/main/scala/Main.scala
src/test/scala/Test.scala
--- a/src/main/scala/Main.scala	Mon Sep 27 09:31:57 2010 -0400
+++ b/src/main/scala/Main.scala	Mon Sep 27 17:46:02 2010 -0400
@@ -6,7 +6,7 @@
 object SQL {
 
   case class Database(m:Map[RelName, Relation])
-  case class Relation (header:Header, body:Body, keys:List[CandidateKey], pk:CandidateKey, fks:ForeignKeys)
+  case class Relation (header:Header, body:Body, keys:List[CandidateKey], pk:Option[CandidateKey], fks:ForeignKeys)
   case class Header (types:Map[AttrName, SQLDatatype]) {
     def keySet () = types.keySet
   }
@@ -17,12 +17,12 @@
 
   type Body = Set[Tuple]
 
+  type Tuple = Map[AttrName, CellValue]
+
   abstract class CellValue
   case class LexicalValue (s:String) extends CellValue
   case class ␀ () extends CellValue
 
-  type Tuple = Map[AttrName, CellValue]
-
   sealed abstract class SQLDatatype
   case class SQLInt () extends SQLDatatype
   case class SQLFloat () extends SQLDatatype
@@ -37,7 +37,7 @@
   type AttrName = String
 
   // Accessor functions:
-  def pk (r:Relation) : List[AttrName] = r.pk
+  def pk (r:Relation) : Option[List[AttrName]] = r.pk
   def header (r:Relation) : Header = r.header
   def body (r:Relation) : Body = r.body
 
@@ -106,29 +106,12 @@
   def databasemap (u:StemIRI, db:Database) : RDFGraph = {
     val idxables:Set[RelName] = db.m.keySet.filter(rn => db.m(rn).keys.size > 0)
     val nodes:NodeMap = idxables.map(rn => rn -> relation2subject(u, rn, db.m(rn))).toMap
-    db.m.keySet.flatMap(rn => relationmap(u, rn, db.m(rn), nodes)).toSet
+    db.m.keySet.flatMap(rn => relationmap(u, rn, db.m(rn), nodes))
   }
 
   def relation2subject (u:StemIRI, rn:RelName, r:Relation) : KeyMap = {
-    val l = List((List("KeyA", "KeyB"), List(10, 11), 1), (List("KeyA", "KeyB"), List(20, 21), 2))
-    val g = Map("KeyA" -> Map("ValA1" -> 1, "ValA2" -> 2), "KeyB" -> Map("ValB1" -> 1, "ValB2" -> 2))
-    l.foldLeft(Map[String, Map[Int, Int]]())((m, t) => {
-      val pairs = t._1.zip(t._2)
-      pairs.foldLeft(m)((m, p) => {
-	if (m.get(p._1).isDefined) {
-	  val byKey = m(p._1)
-	  if (byKey.get(p._2).isDefined) {
-	    error("tried to set " + rn + p._1 + p._2 + " = " + t._3 + "(was " + byKey(p._2) + ")")
-	  } else {
-	    val im1 = byKey ++ Map[Int, Int](p._2 -> t._3)
-	    m ++ Map[String, Map[Int, Int]](p._1 -> im1)
-	  }
-	} else {
-	  m ++ Map(p._1 -> Map(p._2 -> t._3))
-	}
-      })
-    })
 
+    // Here's a built-In test to make sure we don't screw this up:
     val ck1:CandidateKey = List("name", "ssn")
     val ck2:CandidateKey = List("ID")
     val v11:List[CellValue] = List(LexicalValue("bob"), LexicalValue("123"))
@@ -164,6 +147,7 @@
       		     v22 -> s2))
     assert(goal == test)
 
+    // Now the useful invocation:
     val data2:Set[(List[(CandidateKey, List[CellValue])], Node)] = body(r).map(t => {
       // (List(List("name", "ssn"), List("ID")), List(List("bob", 123), List(18)), 1)
       // (List(List("name", "ssn"), List("ID")), List(List("alice", 8), List(23)), 2)
@@ -190,8 +174,12 @@
 
   def tuple2subject (u:StemIRI, rn:RelName, t:Tuple, r:Relation) : (List[(CandidateKey, List[CellValue])], Node) = {
     val h = header(r)
-    val vs = pk(r).map(k => lexvalue(h, t, k).asInstanceOf[LexicalValue])
-    val s:Node = if (pk(r).length == 0) freshbnode() else nodemap(u, rn, pk(r), vs) // Assume: no NULLs in primary key
+    val s:Node =
+      if (r.pk.isDefined) {
+	val vs = r.pk.get.map(k => lexvalue(h, t, k).asInstanceOf[LexicalValue])
+	nodemap(u, rn, r.pk.get, vs) // Assume: no NULLs in primary key
+      } else
+	freshbnode()
     (r.keys.map(k => {
       val values:List[CellValue] = k.map(a => t(a))
       (k, values)
@@ -203,8 +191,14 @@
 
   def tuplemap (u:StemIRI, rn:RelName, t:Tuple, r:Relation, nodes:NodeMap) : Set[Triple] = {
     val h = header(r)
-    val vs = pk(r).map(k => lexvalue(h, t, k).asInstanceOf[LexicalValue])
-    val s:Node = if (nodes.get(rn).isDefined) nodes(rn)(pk(r))(vs) else freshbnode()
+    val s:Node =
+      if (nodes.get(rn).isDefined) {
+	// Known to have at least one key, so take the first one.
+	val k = r.keys(0)
+	val vs = k.map(a => lexvalue(h, t, a).asInstanceOf[LexicalValue])
+	nodes(rn)(k)(vs)
+      } else
+	freshbnode()
 
     val allAttrs:Set[AttrName] = h.keySet
     val allFKs:Set[List[AttrName]] = r.fks.keySet
@@ -224,7 +218,12 @@
 
   }
 
-  def freshbnode () : Node = BNode("999")
+  var NextBNode = 97
+  def freshbnode () : Node = {
+    val ret = NextBNode
+    NextBNode = NextBNode + 1
+    BNode(ret.toChar.toString)
+  }
 
   def scalartriples (u:StemIRI, rn:RelName, s:Node, a:AttrName, h:Header, t:Tuple) : Triple = {
     val p = predicatemap (u, rn, List(a))
@@ -272,6 +271,6 @@
   def literalmap (l:LexicalValue, d:SQLDatatype) : TypedLiteral =
     TypedLiteral(l.s, XSD(d))
 
-  def UE (s:String) : String = s
+  def UE (s:String) : String = s.replaceAll(" ", "+")
 }
 
--- a/src/test/scala/Test.scala	Mon Sep 27 09:31:57 2010 -0400
+++ b/src/test/scala/Test.scala	Mon Sep 27 17:46:02 2010 -0400
@@ -17,7 +17,7 @@
 				     "city" -> LexicalValue("Cambridge"),
 				     "state" -> LexicalValue("MA"))),
 			     List(List("ID")),
-			     List("ID"),
+			     Some(List("ID")),
 			     Map())
 
     val people = Relation(Header(Map("ID" -> SQLInt(),
@@ -30,7 +30,7 @@
 				  "fname" -> LexicalValue("Sue"),
 				  "addr" -> ␀())),
 			  List(List("ID")),
-			  List("ID"),
+			  Some(List("ID")),
 			  Map(List("addr") -> Target("Addresses", List("ID"))))
 
     val db = Database(Map("Addresses" -> addresses, 
@@ -52,6 +52,7 @@
     assert (expected === g)
   }
 
+
   test("2 People 1 Addresses 1 Department") {
 
     val addresses = Relation(Header(Map("ID" -> SQLInt(),
@@ -61,7 +62,7 @@
 				     "city" -> LexicalValue("Cambridge"),
 				     "state" -> LexicalValue("MA"))),
 			     List(List("ID")),
-			     List("ID"),
+			     Some(List("ID")),
 			     Map())
 
     val people = Relation(Header(Map("ID" -> SQLInt(),
@@ -80,7 +81,7 @@
 				  "deptName" -> ␀(),
 				  "deptCity" -> ␀())),
 			  List(List("ID")),
-			  List("ID"),
+			  Some(List("ID")),
 			  Map(List("addr") -> Target("Addresses", List("ID")),
 			      List("deptName", "deptCity") -> Target("Department", List("name", "city"))))
 
@@ -93,7 +94,7 @@
 				     "city" -> LexicalValue("Cambridge"),
 				     "manager" -> LexicalValue("8"))),
 			      List(List("ID"), List("name", "city")),
-			      List("ID"),
+			      Some(List("ID")),
 			      Map(List("manager") -> Target("People", List("ID"))))
 
     val db = Database(Map("Addresses" -> addresses, 
@@ -125,6 +126,135 @@
     assert (expected === g)
   }
 
+
+  test("2 People 1 Addresses 1 Department 2 Projects 1 Task") {
+
+    val addresses = Relation(Header(Map("ID" -> SQLInt(),
+					"city" -> SQLString(),
+					"state" -> SQLString())),
+			     Set(Map("ID" -> LexicalValue("18"),
+				     "city" -> LexicalValue("Cambridge"),
+				     "state" -> LexicalValue("MA"))),
+			     List(List("ID")),
+			     Some(List("ID")),
+			     Map())
+
+    val people = Relation(Header(Map("ID" -> SQLInt(),
+				     "fname" -> SQLString(),
+				     "addr" -> SQLInt(),
+				     "deptName" -> SQLString(),
+				     "deptCity" -> SQLString())),
+			  Set(Map("ID" -> LexicalValue("7"),
+				  "fname" -> LexicalValue("Bob"),
+				  "addr" -> LexicalValue("18"),
+				  "deptName" -> LexicalValue("accounting"),
+				  "deptCity" -> LexicalValue("Cambridge")),
+			      Map("ID" -> LexicalValue("8"),
+				  "fname" -> LexicalValue("Sue"),
+				  "addr" -> ␀(),
+				  "deptName" -> ␀(),
+				  "deptCity" -> ␀())), // no data
+			  List(List("ID")),
+			  Some(List("ID")),
+			  Map(List("addr") -> Target("Addresses", List("ID")),
+			      List("deptName", "deptCity") -> Target("Department", List("name", "city"))))
+
+    val department = Relation(Header(Map("ID" -> SQLInt(),
+					 "name" -> SQLString(),
+					 "city" -> SQLString(),
+					 "manager" -> SQLInt())),
+			      Set(Map("ID" -> LexicalValue("23"),
+				     "name" -> LexicalValue("accounting"),
+				     "city" -> LexicalValue("Cambridge"),
+				     "manager" -> LexicalValue("8"))), // no data
+			      List(List("ID"), List("name", "city")),
+			      Some(List("ID")),
+			      Map(List("manager") -> Target("People", List("ID"))))
+
+    val projects = Relation(Header(Map("lead" -> SQLInt(),
+				       "name" -> SQLString(),
+				       "deptName" -> SQLString(),
+				       "deptCity" -> SQLString())),
+			    Set(Map("lead" -> LexicalValue("8"),
+				    "name" -> LexicalValue("pencil survey"),
+				    "deptName" -> LexicalValue("accounting"),
+				    "deptCity" -> LexicalValue("Cambridge")),
+				Map("lead" -> LexicalValue("8"),
+				    "name" -> LexicalValue("eraser survey"),
+				    "deptName" -> LexicalValue("accounting"),
+				    "deptCity" -> LexicalValue("Cambridge"))),
+			    List(List("lead", "name"), List("name", "deptName", "deptCity")),
+			    None,
+			    /* List(List("name"), List("lead", "name"), List("name", "deptName", "deptCity")),
+			    List("name"), // !!! */
+			    Map(List("lead") -> Target("People", List("ID")),
+				List("deptName", "deptCity") -> Target("Department", List("name", "city"))))
+
+    val tasks = Relation(Header(Map("worker" -> SQLInt(),
+				    "project" -> SQLString(),
+				    "deptName" -> SQLString(),
+				    "deptCity" -> SQLString())),
+			 Set(Map("worker" -> LexicalValue("7"),
+				 "project" -> LexicalValue("pencil survey"),
+				 "deptName" -> LexicalValue("accounting"),
+				 "deptCity" -> LexicalValue("Cambridge"))),
+			 List(List("worker", "project")),
+			 Some(List("worker", "project")),
+			 Map(List("worker") -> Target("People", List("ID")),
+			     List("project", "deptName", "deptCity") -> Target("Projects", List("name", "deptName", "deptCity")),
+			     List("deptName", "deptCity") -> Target("Department", List("name", "city"))))
+
+    val db = Database(Map("Addresses" -> addresses, 
+			  "People" -> people, 
+			  "Department" -> department, 
+			  "Projects" -> projects, 
+			  "TaskAssignments" -> tasks))
+    val g = databasemap(StemIRI("http://foo.example/DB"), db)
+
+    val expected:RDFGraph =
+      Set(
+	Triple(BNode("a"), IRI("http://foo.example/DB/Projects#lead"), IRI("http://foo.example/DB/People/ID.8#_")),
+	Triple(BNode("a"), IRI("http://foo.example/DB/Projects#name"), TypedLiteral("pencil survey", IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(BNode("a"), IRI("http://foo.example/DB/Projects#deptName"), TypedLiteral("accounting", IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(BNode("a"), IRI("http://foo.example/DB/Projects#deptCity"), TypedLiteral("Cambridge", IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(BNode("a"), IRI("http://foo.example/DB/Projects#deptName_deptCity"), IRI("http://foo.example/DB/Department/ID.23#_")),
+
+	Triple(BNode("b"), IRI("http://foo.example/DB/Projects#lead"), IRI("http://foo.example/DB/People/ID.8#_")),
+	Triple(BNode("b"), IRI("http://foo.example/DB/Projects#name"), TypedLiteral("eraser survey", IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(BNode("b"), IRI("http://foo.example/DB/Projects#deptName"), TypedLiteral("accounting", IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(BNode("b"), IRI("http://foo.example/DB/Projects#deptCity"), TypedLiteral("Cambridge", IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(BNode("b"), IRI("http://foo.example/DB/Projects#deptName_deptCity"), IRI("http://foo.example/DB/Department/ID.23#_")),
+
+	Triple(IRI("http://foo.example/DB/TaskAssignments/worker.7_project.pencil+survey#_"), IRI("http://foo.example/DB/TaskAssignments#worker"), IRI("http://foo.example/DB/People/ID.7#_")),
+	Triple(IRI("http://foo.example/DB/TaskAssignments/worker.7_project.pencil+survey#_"), IRI("http://foo.example/DB/TaskAssignments#project"), TypedLiteral("pencil survey", IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(IRI("http://foo.example/DB/TaskAssignments/worker.7_project.pencil+survey#_"), IRI("http://foo.example/DB/TaskAssignments#deptName"), TypedLiteral("accounting", IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(IRI("http://foo.example/DB/TaskAssignments/worker.7_project.pencil+survey#_"), IRI("http://foo.example/DB/TaskAssignments#deptCity"), TypedLiteral("Cambridge", IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(IRI("http://foo.example/DB/TaskAssignments/worker.7_project.pencil+survey#_"), IRI("http://foo.example/DB/TaskAssignments#deptName_deptCity"), IRI("http://foo.example/DB/Department/ID.23#_")),
+	Triple(IRI("http://foo.example/DB/TaskAssignments/worker.7_project.pencil+survey#_"), IRI("http://foo.example/DB/TaskAssignments#project_deptName_deptCity"), BNode("a")),
+
+	Triple(IRI("http://foo.example/DB/People/ID.7#_"),IRI("http://foo.example/DB/People#deptName"),TypedLiteral("accounting",IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(IRI("http://foo.example/DB/People/ID.7#_"),IRI("http://foo.example/DB/People#deptCity"),TypedLiteral("Cambridge",IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(IRI("http://foo.example/DB/People/ID.7#_"),IRI("http://foo.example/DB/People#deptName_deptCity"),IRI("http://foo.example/DB/Department/ID.23#_")),
+
+	Triple(IRI("http://foo.example/DB/Department/ID.23#_"),IRI("http://foo.example/DB/Department#ID"),TypedLiteral("23",IRI("http://www.w3.org/2001/XMLSchema#int"))),
+	Triple(IRI("http://foo.example/DB/Department/ID.23#_"),IRI("http://foo.example/DB/Department#name"),TypedLiteral("accounting",IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(IRI("http://foo.example/DB/Department/ID.23#_"),IRI("http://foo.example/DB/Department#city"),TypedLiteral("Cambridge",IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(IRI("http://foo.example/DB/Department/ID.23#_"),IRI("http://foo.example/DB/Department#manager"),IRI("http://foo.example/DB/People/ID.8#_")),
+
+	Triple(IRI("http://foo.example/DB/People/ID.7#_"),IRI("http://foo.example/DB/People#ID"),TypedLiteral("7",IRI("http://www.w3.org/2001/XMLSchema#int"))),
+	Triple(IRI("http://foo.example/DB/People/ID.7#_"),IRI("http://foo.example/DB/People#fname"),TypedLiteral("Bob",IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(IRI("http://foo.example/DB/People/ID.7#_"),IRI("http://foo.example/DB/People#addr"),IRI("http://foo.example/DB/Addresses/ID.18#_")),
+	Triple(IRI("http://foo.example/DB/People/ID.8#_"),IRI("http://foo.example/DB/People#ID"),TypedLiteral("8",IRI("http://www.w3.org/2001/XMLSchema#int"))),
+	Triple(IRI("http://foo.example/DB/People/ID.8#_"),IRI("http://foo.example/DB/People#fname"),TypedLiteral("Sue",IRI("http://www.w3.org/2001/XMLSchema#string"))), 
+
+	Triple(IRI("http://foo.example/DB/Addresses/ID.18#_"),IRI("http://foo.example/DB/Addresses#ID"),TypedLiteral("18",IRI("http://www.w3.org/2001/XMLSchema#int"))),
+	Triple(IRI("http://foo.example/DB/Addresses/ID.18#_"),IRI("http://foo.example/DB/Addresses#city"),TypedLiteral("Cambridge",IRI("http://www.w3.org/2001/XMLSchema#string"))),
+	Triple(IRI("http://foo.example/DB/Addresses/ID.18#_"),IRI("http://foo.example/DB/Addresses#state"),TypedLiteral("MA",IRI("http://www.w3.org/2001/XMLSchema#string")))
+      )
+    assert (expected === g)
+  }
+
+
   test("2 Employees") {
     val employees = Relation(Header(Map("ID" -> SQLInt(),
 					"fname" -> SQLString(),
@@ -137,7 +267,7 @@
 				     "boss" -> LexicalValue("1"))
 			       ),
 			     List(List("ID")),
-			     List("ID"),
+			     Some(List("ID")),
 			     Map(List("boss") -> Target("Employees", List("ID"))))
     val db = Database(Map("Employees" -> employees))
     val g = databasemap(StemIRI("http://foo.example/DB"), db)