W3C

A Direct Mapping of Relational Data to RDF

W3C Working Draft 18 November 2010

This version:
http://www.w3.org/TR/2010/WD-rdb-direct-mapping-20101118/
Latest version:
http://www.w3.org/TR/rdb-direct-mapping/
Editors:
Marcelo Arenas, Pontificia Universidad Católica de Chile <marenas@ing.puc.cl>
Eric Prud'hommeaux, W3C <eric@w3.org>
Juan Sequeda, University of Texas at Austin <jsequeda@cs.utexas.edu>

Abstract

The need to share data with collaborators motivates custodians and users of relational databases (RDB) to expose relational data on the Web of Data. This document defines a direct mapping from relational data to RDF. This definition provides extension points for refinements within and outside of this document.

Status of this Document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at http://www.w3.org/TR/.

This is the First Public Working Draft of "A Direct Mapping of Relational Data to RDF" for review by W3C members and other interested parties. Sections 3 and 4 provide two normative definitions of the mapping from a relational database to an RDF graph. The WG would like feedback on which route to choose and learn about potential advantages and disadvantages of the proposals.

Publication as a Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

The W3C RDB2RDF Working Group is responsible for this document. It includes the RDF Schema that can be used to specify a mapping of relational data to RDF. The structure of this document will change based upon future decisions taken by the W3C RDB2RDF Working Group. The Working Group is also working on a document that will define a default mapping from relational databases to RDF. The Working Group hopes to publish the default mapping document shortly.

Comments on this document should be sent to public-rdb2rdf-comments@w3.org, a mailing list with a public archive.

This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

Table of Contents

1 Introduction
2 Direct Mapping Description (Informative)
2.1 Direct Mapping Example
2.2 Preliminaries: Generating IRIs
2.2.1 IRIs generated for the initial example
2.3 Mapping Rules
2.3.1 Triples generated for the example in Section Direct Mapping Example
2.4 Additional Examples and Corner Cases
2.4.1 Foreign keys referencing candidate keys
2.4.2 Multi-column keys
2.4.3 Empty (non-existent) primary keys
2.4.4 Referencing tables with empty primary keys
2.5 Hierarchical Tables
3 Direct Mapping Definition (Normative)
3.1 Notation for this Direct Mapping
3.1.1 Notation for Types
3.1.2 Notation for Injectors
3.1.3 Accessor Functions
3.2 Data Model Definition (Normative)
3.2.1 Reference Database Model Definition (Normative)
3.2.2 RDF Model Definition (Non-normative)
4 Direct Mapping as Rules (Normative)
4.1 Generating Table Triples
4.1.1 Table has a single-column primary key
4.1.2 Table has a multi-column primary key
4.1.3 Table does not have a primary key
4.2 Generating Literal Triples
4.2.1 Table has a single-column primary key
4.2.2 Table has a multi-column primary key
4.2.3 Table does not have a primary key
4.3 Generating Reference Triples
5 References

Appendix

A CVS History


1 Introduction

Relational databases proliferate both because of their efficiency and their precise definitions, allowing for tools like SQL [SQLFN] to manipulate and examine the contents predictably and efficiently. Resource Description Framework (RDF) [RDF] is a data format based on a web-scalable architecture for identification and interpretation of terms. This document defines a mapping from relational representation to an RDF representation.

Strategies for mapping relational data to RDF abound. The direct mapping defines a simple transformation, providing a basis for defining and comparing more intricate transformations. This document includes an informal and a formal description of the transformation.

The Direct Mapping is intended to provide a default behavior for R2RML: RDB to RDF Mapping Language. It can be also used to materialize RDF graphs or define virtual graphs, which can be queried by SPARQL or traversed by an RDF graph API.

2 Direct Mapping Description (Informative)

The direct mapping defines an RDF Graph [RDF] representation of the data in any relational database. The direct mapping takes as input a relational database (data and schema), and generates an RDF graph that is called the direct graph. This graph is composed of relative IRIs that may be resolved against a base IRI per [RFC3987]. Foreign keys in relational databases establish a named reference from any row in a table to exactly one row in a (potentially different) table. The direct graph conveys these references, as well as each value in the rows.

2.1 Direct Mapping Example

The concepts in direct mapping can be introduced with an example RDF graph produced by a relational database. Following is SQL (DDL) to create a simple example with two tables with single-column primary keys and one foreign key reference between them:

CREATE TABLE Addresses (
	ID INT, 
	city CHAR(10), 
	state CHAR(2), 
	PRIMARY KEY(ID)
)

CREATE TABLE People (
	ID INT, 
	fname CHAR(10), 
	addr INT, PRIMARY KEY(ID), 
	FOREIGN KEY(addr) REFERENCES Addresses(ID)
)

INSERT INTO Addresses (ID, city, state) VALUES (18, "Cambridge", "MA")
INSERT INTO People (ID, fname, addr) VALUES (7, "Bob", 18)
INSERT INTO People (ID, fname, addr) VALUES (8, "Sue", NULL)
      

HTML tables will be used in this document to convey SQL tables. The primary key of these tables will be marked with the PK class to convey an SQL primary key such as ID in CREATE TABLE Addresses (ID INT, ... PRIMARY KEY(ID)). Foreign keys will be illustrated with a notation like "→ Address(ID)" to convey an SQL foreign key such as CREATE TABLE People (... addr INT, FOREIGN KEY(addr) REFERENCES Addresses(ID)).

People
PK→ Address(ID)
IDfnameaddr
7Bob18
8SueNULL
Addresses
PK
IDcitystate
18CambridgeMA

Given a base IRI http://foo.example/DB/, the direct mapping of this database produces a direct graph:

@base <http://foo.example/DB/>
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .


<People/ID=7#_> <rdf:type> <People> .
<People/ID=7#_> <People#ID> 7 .
<People/ID=7#_> <People#fname> "Bob" .
<People/ID=7#_> <People#addr> <Addresses/ID=18#_> .
<People/ID=8#_> <rdf:type> <People> .
<People/ID=8#_> <People#ID> 8 .
<People/ID=8#_> <People#fname> "Sue" .

<Addresses/ID=18#_> <rdf:type> <Addresses> .
<Addresses/ID=18#_> <Addresses#ID> 18 .
<Addresses/ID=18#_> <Addresses#city> "Cambridge" .
<Addresses/ID=18#_> <Addresses#state> "MA" .
      

In this expression, each row, e.g. (7, "Bob", 18), produces a set of triples with a common subject. The subject is an IRI formed from the concatenation of the base IRI, table name (People), primary key column name (ID) and primary key value (7). The predicate for each column is an IRI formed from the concatenation of the base IRI, table name and the column name. The values are either RDF literals formed from the lexical form of the column value, or, in the case of foreign keys, row identifiers (<Addresses/ID=18#_>). Note that these reference row identifiers must coincide with the subject used for the triples generated from the referenced row.

2.2 Preliminaries: Generating IRIs

In the process of translating relational data into RDF, the direct mapping must create IRIs for identifying tables, the columns in a table, and each row in a table. In this section, we assume that http://foo.example/DB is the the base IRI. All the examples in this section will contain relative IRIs which are to be understood as relative to this base IRI. The following are the IRIs that need to be generated:

  • Table IRI: The IRI that identifies a table is created by concatenating the base IRI with the table name. Specifically, if base_IRI is the base IRI and table_name is the table name, then base_IRI/table_name is the Table IRI for the table.
  • Column IRI:
    • Single-column IRI: The IRI that identifies a column of a table is created by concatenating the base IRI with the table name and the column name. Specifically, if base_IRI is the base IRI, table_name is the table name and column_name is the column name, then base_IRI/table_name#column_name is the Column IRI for the column.
    • Multi-column IRI: The IRI that identifies a sequence of two or more columns of a table is created by concatenating the base IRI with the table name and the column names. Specifically, if base_IRI is the base IRI, table_name is the table name and column_name_1, column_name_2, ..., column_name_k is a sequence of k columns (k > 1), then base_IRI/table_name#column_name_1,column_name_2,...,column_name_k is the Column IRI for the columns.
  • Row RDF Node:
    • Row RDF Node for a row with a single-column primary key: The IRI that identifies a row is created by concatenating the base IRI with the table name, the column name of the primary key and the value of the row in that column. Specifically, if base_IRI is the base IRI, table_name is the table name, column_name is the column name of the primary key and value is the value of the row in that column, then base_IRI/table_name/column_name=value#_ is the Row RDF Node (or Row IRI) for the row.
    • Row RDF Node for a row with a multi-column primary key: The IRI that identifies a row is created by concatenating the base IRI with the table name, the names of the columns that constitute the primary key and the values of the row in those columns. Specifically, if base_IRI is the base IRI, table_name is the table name, column_name_1, column_name_2, ..., column_name_k is the sequence of k columns (k > 1) that constitute the primary key, and value_1, value_2, ..., value_k is the sequence of values of the columns that constitute the primary key of the row, then base_IRI/table_name/column_name_1=value_1,column_name_2=value_2,...,column_name_k=value_k#_ is the Row RDF Node (or Row IRI) for the row.
    • Row RDF Node for a row without a primary key: A fresh Blank Node is created, which is used as the Row RDF Node for the row.

Issue (hash-vs-slash):

The direct graph may be offered as Linked Open Data, raising the issue of distinguishing row identifiers from the information resources which describe them. This edition of this document presumes hash identifiers, allowing a GET on a row identifier to retrieve a small resource (i.e. not all rows from the same table) and distinguish between the retrieved resource People/ID=7 and the row People/ID=7#_. The "slash" alternative would offer a direct graph with identifiers like People/ID=7 but would demand the server respond to GET /People/ID=7 with a 303 redirect to some other resource.

Resolution:

None recorded.

2.2.1 IRIs generated for the initial example

Given the base IRI http://foo.example/DB/, the following are some of the IRIs that are used when translating into RDF the relational data given in the initial example:

  • For the table People, the following IRIs are considered in the translation process:

    • Table IRI:

      <People> 
                           
    • Column IRIs:

      <People#ID> 
      <People#fname> 
      <People#addr>  
                           
    • Row IRIs:

      <People/ID=7#_> 
      <People/ID=8#_>
                           
  • For the table Addresses, the following IRIs are considered in the translation process:

    • Table IRI:

      <Addresses> 
                           
    • Columns IRIs:

      <Addresses#ID> 
      <Addresses#city>  
      <Addresses#state> 
                           
    • Row IRI:

      <Addresses/ID=18#_>
                           

2.3 Mapping Rules

Each row in the database produces a set of RDF triples with a subject, predicate, and object composed as follows:

  • Shared Subject: A Row RDF Node, which may be an IRI or a Blank Node, is generated for each row.
  • Table Triples: The row generates a triple with the following:
    • Predicate: the rdf:type property
    • Object: the Table IRI for the table
  • Literal Triples: Each column with a non-null value, including the column(s) that constitute the primary key, and that either is not the only constituent of a foreign key or is the only constituent of a foreign key that references a candidate key, generates a triple with the following:
  • Reference Triples: Columns that constitute a foreign key and with non-null values in the row generate triples with the following:
    • Predicate: the Column IRI for the columns that constitute the foreign key
    • Object: the Row RDF Node for the corresponding referenced row (according to the foreign key)

Issue (primary-is-candidate-key):

Should the following exception be included in the definition of the direct mapping?

Primary-is-Candidate-Key Exception: If the primary key is also a candidate key K to table R:

  • The shared subject is the subject of the referenced row in R.
  • The foreign key K generates no reference triple.
  • Even if K is a single-column foreign key, it generates a literal triple.

Resolution:

None recorded.

2.3.1 Triples generated for the example in Section Direct Mapping Example

Next we show how the 11 triples in the example of Section Direct Mapping Example are classified into the above categories:

  • Triples generated from table People:

    • Table Triples:

      <People/ID=7#_> <rdf:type> <People> .              
      <People/ID=8#_> <rdf:type> <People> .
                           
    • Literal Triples:

      <People/ID=7#_> <People#ID> 7 .
      <People/ID=7#_> <People#fname> "Bob" .
      <People/ID=8#_> <People#ID> 8 .
      <People/ID=8#_> <People#fname> "Sue" .
                           
    • Reference Triple:

      <People/ID=7#_> <People#addr> <Addresses/ID=18#_> .
                           
  • Triples generated from table Addresses:

    • Table Triple:

      <Addresses/ID=18#_> <rdf:type> <Addresses> .
                           
    • Literal Triples:

      <Addresses/ID=18#_> <Addresses#ID> 18 .
      <Addresses/ID=18#_> <Addresses#city> "Cambridge" .
      <Addresses/ID=18#_> <Addresses#state> "MA" .
                           

2.4 Additional Examples and Corner Cases

2.4.1 Foreign keys referencing candidate keys

More complex schemas include compound and composite primary keys. In this example, the columns deptName and deptCity in the People table reference name and city in the Department table. The following is the schema of the augmented database:

CREATE TABLE Addresses (
	ID INT, 
	city CHAR(10), 
	state CHAR(2), 
	PRIMARY KEY(ID)
)

CREATE TABLE Deparment (
	ID INT, 
	name CHAR(10), 
	city CHAR(10), 
	manager INT, 
	PRIMARY KEY(ID), 
	UNIQUE (name, city), 
	FOREIGN KEY(manager) REFERENCES People(ID)
)

CREATE TABLE People (
	ID INT, 
	fname CHAR(10), 
	addr INT, 
	deptName CHAR(10), 
	deptCity CHAR(10), 
	PRIMARY KEY(ID), 
	FOREIGN KEY(addr) REFERENCES Addresses(ID), 
	FOREIGN KEY(deptName, deptCity) REFERENCES Department(name, city) 
)
         

The following is an instance of the augmented relational schema:

People
PK → Addresses(ID)→ Department(name, city)
IDfnameaddrdeptNamedeptCity
7Bob18accountingCambridge
8SueNULLNULLNULL
Addresses
PK
IDcitystate
18CambridgeMA
Department
PKUnique Key→ People(ID)
IDnamecitymanager
23accountingCambridge8

Per the People tables's compound foreign key to Department:

  • The row in People with deptName="accounting" and deptCity="Cambridge" references a row in Department with a primary key of ID=23.
  • The predicate for this key is formed from "deptName,deptCity", reflecting the order of the column names in the foreign key.
  • The referent identifier (object of the above predicate) is formed from the base IRI and "ID=23".

In this example, the direct mapping generates the following triples:

@base <http://foo.example/DB/>
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<People/ID=7#_> <rdf:type> <People> .
<People/ID=7#_> <People#ID> 7 .
<People/ID=7#_> <People#fname> "Bob" .
<People/ID=7#_> <People#addr> <Addresses/ID=18#_> .
<People/ID=7#_> <People#deptName> "accounting" .
<People/ID=7#_> <People#deptCity> "Cambridge" .
<People/ID=7#_> <People#deptName,deptCity> <Department/ID=23#_> .
<People/ID=8#_> <rdf:type> <People> .
<People/ID=8#_> <People#ID> 8 .
<People/ID=8#_> <People#fname> "Sue" .

<Addresses/ID=18#_> <rdf:type> <Addresses> .
<Addresses/ID=18#_> <Addresses#ID> 18 .
<Addresses/ID=18#_> <Addresses#city> "Cambridge" .
<Addresses/ID=18#_> <Addresses#state> "MA" .

<Department/ID=23#_> <rdf:type> <Department> .
<Department/ID=23#_> <Department#ID> 23 .
<Department/ID=23#_> <Department#name> "accounting" .
<Department/ID=23#_> <Department#city> "Cambridge" .
<Department/ID=23#_> <Department#manager> <People#ID=8#_> .
	

The green triples above are generated by considering the new elements in the augmented database. It should be noticed that:

  • Although deptName is an attribute of table People that is part of a foreign key, the Literal Triple <People/ID=7#_> <People#deptName> "accounting" is generated by the direct mapping because deptName is not the sole column of a foreign key of table People.

  • The Reference Triple <People/ID=7#_> <People#deptName,deptCity> <Department/ID=23#_> is generated by considering a foreign key referencing a candidate key (instead of the primary key): (deptName, deptCity) is a multi-column foreign key in the table People which references the multi-column candidate key (name, city) in the table Department.

2.4.2 Multi-column keys

We note that primary keys may also be composite. For example, if the primary key for Department were (name, city) instead of ID in the example in Section Foreign keys referencing candidate keys, then the identifier for the only row in this table would be <Department/name=accounting,city=Cambridge>, and the following triples would have been generated by the direct mapping:

<Department/name=accounting,city=Cambridge#_> <rdf:type> <Department> . 
<Department/name=accounting,city=Cambridge#_> <Department#ID> 23 . 
<Department/name=accounting,city=Cambridge#_> <Department#name> "accounting" .
<Department/name=accounting,city=Cambridge#_> <Department#city> "Cambridge" .
            

2.4.3 Empty (non-existent) primary keys

Even if there is no primary key, rows generate a set of triples with a shared subject, but that subject is a blank node. For instance, assume that the following table is added to the schema of the example in Section Foreign keys referencing candidate keys (for keeping track of tweets in Twitter):

CREATE TABLE Tweets (
	tweeter INT,
	when TIMESTAMP,
	text CHAR(140),
	FOREIGN KEY(tweeter) REFERENCES People(ID)
)
            

The following is an instance of table Tweets:

Tweets
→ People(ID)
tweeterwhentext
72010-08-30T01:33I really like lolcats.
72010-08-30T09:01I take it back.

Given that table Tweets does not have a primary key, each row in this table is identified by a Blank Node. In fact, when translating the above table the direct mapping generates the following triples:

@base <http://foo.example/DB/>
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

_:a <rdf:type> <Tweets> .
_:a <Tweets#tweeter> <People/ID=7#_> .
_:a <Tweets#when> "2010-08-30T01:33"^^xsd:dateTime .
_:a <Tweets#text> "I really like lolcats." .

_:b <rdf:type> <Tweets> .
_:b <Tweets#tweeter> <People/ID=7#_> .
_:b <Tweets#when> "2010-08-30T09:01"^^xsd:dateTime .
_:b <Tweets#text> "I take it back." .
	

It is not possible to dereference blank nodes ("_:a" and "_:b" above). Queries or updates may be made to these nodes via SPARQL queries.

2.4.4 Referencing tables with empty primary keys

Rows in tables with no primary key may still be referenced by foreign keys. (Relational database theory tells us that these rows must be unique as foreign keys reference candidate keys and candidate keys are unique across all the rows in a table.) References to rows in tables with no primary key are expressed as RDF triples with blank nodes for objects, where that blank node is the same node used for the subject in the referenced row.

This example includes several foreign keys with mutual column names. For clarity; here is the DDL to clarify these keys:

CREATE TABLE Projects (
	lead INT,
        FOREIGN KEY (lead) REFERENCES People(ID),
        name VARCHAR(50), 
        UNIQUE (lead, name), 
        deptName VARCHAR(50), 
        deptCity VARCHAR(50),
        UNIQUE (name, deptName, deptCity),
        FOREIGN KEY (deptName, deptCity) REFERENCES Department(name, city)
)

CREATE TABLE TaskAssignments (
	worker INT,
        FOREIGN KEY (worker) REFERENCES People(ID),
        project VARCHAR(50), 
        PRIMARY KEY (worker, project), 
        deptName VARCHAR(50), 
        deptCity VARCHAR(50),
        FOREIGN KEY (worker) REFERENCES People(ID),
        FOREIGN KEY (project, deptName, deptCity) REFERENCES Projects(name, deptName, deptCity),
        FOREIGN KEY (deptName, deptCity) REFERENCES Department(name, city)
)
	  

The following is an instance of the preceding schema:

Projects
Unique key
Unique key
→ People(ID)→ Department(name, city)
leadnamedeptNamedeptCity
8pencil surveyaccountingCambridge
8eraser surveyaccountingCambridge
TaskAssignments
PK
→ Projects(name, deptName, deptCity)
→ People(ID)→ Departments(name, city)
workerprojectdeptNamedeptCity
7pencil surveyaccountingCambridge

In this case, the direct mapping generates the following triples from the preceding tables:

@base <http://foo.example/DB/>
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix pencil: <http://foo.example/DB/TaskAssignment/worker=7,project=pencil+survey#_> .

_:c <rdf:type> <Projects> .
_:c <Projects#lead> <People/ID=8#_> .
_:c <Projects#name> "pencil survey" .
_:c <Projects#deptName> "accounting" .
_:c <Projects#deptCity> "Cambridge" .
_:c <Projects#deptName,deptCity> <Department/ID=23#_> .

_:d <rdf:type> <Projects> .
_:d <Projects#lead> <People/ID=8#_> .
_:d <Projects#name> "eraser survey" .
_:d <Projects#deptName> "accounting" .
_:d <Projects#deptCity> "Cambridge" .
_:d <Projects#deptName,deptCity> <Department/ID=23#_> .

pencil:_ <rdf:type> <TaskAssignments> .
pencil:_ <TaskAssignments#worker> <People/ID=7#_> .
pencil:_ <TaskAssignments#project> "pencil survey" .
pencil:_ <TaskAssignments#deptName> "accounting" .
pencil:_ <TaskAssignments#deptCity> "Cambridge" .
pencil:_ <TaskAssignments#deptName,deptCity> <Department/ID=23#_> .
pencil:_ <TaskAssignments#project,deptName,deptCity> _:c .
	  

The absence of a primary key forces the generation of blank nodes, but does not change the structure of the direct graph or names of the predicates in that graph.

2.5 Hierarchical Tables

It is common to express specializations of some concept as multiple tables sharing a common primary key. In such cases, the primary keys of the inherited tables are in turn foreign keys to the table from which they derive.

Addresses
PK
IDcitystate
18CambridgeMA
Offices
PK
→ Addresses(ID)
IDbuildingofcNumber
1832G528
ExecutiveOffices
PK
→ Offices(ID)
IDdesk
18oak

In this example, Offices are a specialization of Addresses and ExecutiveOffices are a specialization of Offices. The subjects for the triples implied by rows in Offices or ExecutiveOffices are the same as those for the corresponding row in Addresses.

@base <http://foo.example/DB/>
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<Addresses/ID=18#_> <Addresses#ID> 18 .
<Addresses/ID=18#_> <Addresses#city> "Cambridge" .
<Addresses/ID=18#_> <Addresses#state> "MA" .

<Addresses/ID=18#_> <Offices#ID> 18 .
<Addresses/ID=18#_> <Offices#building> 32 .
<Addresses/ID=18#_> <Offices#ofcNumber> "G528" .

<Addresses/ID=18#_> <ExecutiveOffices#ID> 18 .
<Addresses/ID=18#_> <ExecutiveOffices#desk> "oak" .
	

The Primary-is-foreign Key Exception allows the generation of a triple with an RDF literal for the ID column in the Offices and ExecutiveOffices table (Offices.ID=18 and ExecutiveOffices.ID=18).

Issue (hier-table-at-risk):

This feature attempts to intricately model some existing modeling practice but adds significant complexity. This feature is at risk.

Resolution:

None recorded.

Issue (fk-pk-order):

What if fk is a rearrangement of the pk? E.g what if TaskAssignments, with a primary key (project, worker), had a foreign key (worker, project)?

Resolution:

None recorded.

Issue (many-to-many-as-repeated-properties):

The direct graph is arguably more faithful to the conceptual model if it reflects e.g. a person with multiple addresses (some many-to-many Person2Address table) as repeated properties. It is difficult to detect which tables with exactly two foreign keys and no other attributes are many-to-many. As a counter example, a Wedding table may have exactly two spouses but it's still not a many-to-many relation in most places.

Resolution:

None recorded.

Issue (formalism-model):

The RDB2RDF working group has not decided on a formalism for representing the direct mapping. We would appreciate feedback from the community in helping us choose between Section 5. Direct Mapping Definition and Section 6. Direct Mapping as Rules.

Resolution:

None recorded.

3 Direct Mapping Definition (Normative)

3.1 Notation for this Direct Mapping

3.1.1 Notation for Types

A : a type

A? : an optional argument of type A

A ⊔ B : disjoint union of A and B

( A, B ) : tuple (Cartesian product) of types A and B

[ A ] : list of elements of type A

{ A } : set of elements of type A

{ A→B } : finite map of elements of type A to elements of type B

3.1.2 Notation for Injectors

a : an instance of an A

( a1, b1 ) : a tuple with elements a1 and b1

[ a1, a2 ] : list with elements a1 and a2

{ a1, a2 } : set with elements a1 and a2

{ a1→b1, a2→b2 } : map with elements with key a1 mapped to b1 and key a2 mapped to b2

3.1.3 Accessor Functions

AB[a] : in a map of A to B, the instance of B for a given A

3.2 Data Model Definition (Normative)

The buttons below can be used to show or hide the available syntaxes.

3.2.1 Reference Database Model Definition (Normative)

There are many models for databases in SQL literature; because the Direct Mapping does not rely on column position, we use a model which assumes a 1:1 correspondance between attribute (column name) and value, i.e. a map. Starting with a traditional model of a relational database we define a Relation (a table) which has a name, a Header, Body and primary/foreign key details. The Body contains maps from attribute names to values and the Header provides the datatypes to interpret those values.

Relational Definition
[1] Database{ TableNameTable }
A relational database is a mapping of relation name to relation.
  case class Database( m:Map[TableName, Table] )
[2] Table( Header, [CandidateKey], CandidateKey?, ForeignKeys, Body ) where the 2nd slot is a list of candidate keys that apply to the table, and the 3rd is an optional candidate key use as the primary key
A relation has a header, a list of candidate keys, a primary key (of type candidate key), a mapping of foreign keys, and a body.
  case class Table (header:Header, body:Body, candidates:List[CandidateKey], pk:Option[CandidateKey], fks:ForeignKeys)
[3] Header{ AttrNameSQLDatatype }
A header is a mapping from attribute name to SQL datatype.
  case class Header (types:Map[AttrName, SQLDatatype])
[4] CandidateKey[ AttrName ]
A candidate key is a list of attribute names.
  type CandidateKey = List[AttrName]
[5] ForeignKeys{ [AttrName] → ( Table, [AttrName] ) }
Foreign keys is a mapping from a list of attribute names to a relation and a list of attribute names.
  type ForeignKeys = Map[AttrName, Target]
  case class Target (rel:TableName, attrs:CandidateKey)
[6] SQLDatatype{ INT | FLOAT | DATE | TIME | TIMESTAMP | CHAR | VARCHAR | STRING }
An SQL datatype is an INT, FLOAT, DATE, TIME, TIMESTAMP, CHAR, VARCHAR or STRING as defined in the SQL specification .
  sealed abstract class SQLDatatype
  case class SQLInt () extends SQLDatatype
  case class SQLFloat () extends SQLDatatype
  …
  case class SQLString () extends SQLDatatype
[7] Body[ Tuple ]
A body is a list of (potentially duplicate) tuples.
  type Body = List[Tuple]
[8] Tuple{ AttrNameCellValue }
A tuple is a mapping from attribute name to cell value.
  case class Tuple (m:Map[AttrName, CellValue])
[9] CellValuevalue | Null
A cell value is a scalar value in some SQL datatype, or SQL NULL.
  abstract class CellValue
  case class LexicalValue (s:String) extends CellValue
  case class ␀ () extends CellValue

3.2.2 RDF Model Definition (Non-normative)

Per RDF Concepts and Abstract Syntax, an RDF graph is a set of triples of a subject, predicate and object. The subject may be an IRI or a blank node, the predicate must be an IRI and the object may be an IRI, blank node, or an RDF literal.

This section recapitulates for convience the formal definition of RDF.

RDF Definition
[10] Graph{ Triple }
An RDF graph is a set of RDF triples.
  type RDFGraph = Set[Triple]
[11] Triple( Subject, Predicate, Object )
An RDF triple contains a subject, predicate and object.
  case class Triple (s:Subject, p:IRI, o:Object)
[12] SubjectIRI | BlankNode
A subject is an IRI or a blank node.
  sealed abstract class Node // factor out IRIs and BNodes
  case class NodeIRI(i:IRI) extends Node
  case class NodeBNode(b:BNode) extends Node

  sealed abstract class Subject
  case class SubjectNode(n:Node) extends Subject
[13] PredicateIRI
A predicate is an IRI.
  sealed abstract class Predicate
  case class PredicateIRI(i:IRI) extends Predicate
[14] ObjectIRI | BlankNode | Literal
An object is an IRI, a blank node, or a literal.
  sealed abstract class Object
  case class ObjectNode(n:Node) extends Object
  case class ObjectLiteral (n:Literal) extends Object
[15] IRIRDF URI-reference as subsequently restricted by SPARQL
An IRI is an RDF URI reference as subsequently restricted by SPARQL.
  case class IRI(iri:String)
[16] BlankNodeRDF blank node
A blank node is an arbitrary term used only to establish graph connectivity.
  case class BNode(label:String)
[17] LiteralPlainLiteral | TypedLiteral
A literal is either a plain literal or a typed literal.
  sealed abstract class Literal
  case class LiteralTyped(i:TypedLiteral) extends Literal
  case class LiteralPlain(b:PlainLiteral) extends Literal
[18] PlainLiteral(lexicalForm) | (lexicalForm, langageTag).
A plain literal has a lexical form and an optional language tag.
  case class PlainLiteral(value:String, langtag:Option[String])
[19] TypedLiteral(lexicalForm, IRI).
An typed literal has a lexical form and a datatype IRI.
  case class TypedLiteral(value:String, datatype:IRI)

The direct mapping is a formula for creating an RDF graph from the tuples in a relation. A base IRI defines a web space for the labels in this graph; all labels are generated by appending to the base. The functions scalar and reference extract the non-Null scalar and reference attributes respectively.

[20] references(T, R){ K ∣ ∄(T(A) = Null ∣ A ∈ K) ∧ K ≠ R.PrimaryKey ∣ K ∈ R.ForeignKeys }
The references function returns the attributes in any of a relation's foreign keys.
  def references (t:Tuple, r:Table):Set[List[AttrName]] = {
    val allFKs:Set[List[AttrName]] = r.fks.keySet
    val nulllist:Set[AttrName] = t.nullAttributes(r.header)
    val nullFKs:Set[List[AttrName]] = allFKs.flatMap(a => {
      val int:Set[AttrName] = nulllist & a.toSet
      if (int.toList.length == 0) None else List(a)
    })

    /** Check to see if r's primary key is a hierarchical key.
     * http://www.w3.org/2001/sw/rdb2rdf/directMapping/#rule3 */
    if (r.pk.isDefined && r.fks.contains(r.pk.get))
      r.fks.keySet -- nullFKs - r.fks(r.pk.get).attrs
    else
      r.fks.keySet -- nullFKs
  }
[21] scalars(T){ A in T ∣ A ≠ Null ∧ [A] ∉ references(T) }
The scalars function returns the attributes which are NOT in any of a relation's foreign keys.
  def scalars (t:Tuple, r:Table):Set[AttrName] = {
    val allAttrs:Set[AttrName] = r.header.keySet
    val nulllist:Set[AttrName] = t.nullAttributes(r.header)
    val refs = references(t, r) filter (a => a.length == 1) map (a => a(0))
    allAttrs -- refs -- nulllist
  }

Each tuple in a relation with some candidate key can be uniquely identified by values of that key. A KeyMap(R) maps the candidate keys in a relation to a map of key values to the subject nodes assigned to each tuple.

[22] KeyMap{ CandidateKey → { [CellValue] → RDF Node } }
A KeyMap is a map from candidate key to a map from list of cell values to RDF nodes.
  type KeyMap = Map[CandidateKey, Map[List[CellValue], Node]]

The function directDB(DB) computes a RowIRI M for each relation with one or more candidate keys. The function directR(R, M) maps the Tuples in a Table R to an RDF graph. The following definitions assume the existance of some BaseIRI U and Database DB.

[23] directDB(){ directR(R, M) ∣ R ∈ DB }
The directDB of a database DB is a set of RDF triples (RDF graph) created by calling directR on each relation in DB.
  def directDB (u:BaseIRI, db:Database) : RDFGraph = {
    val idxables = db.keySet filter { rn => !db(rn).candidates.isEmpty }
    val rowIRI = idxables map {rn => rn -> relation2KeyMap(u, db(rn))}
    db.keySet.flatMap(rn => directR(u, db(rn), rowIRI, db))
  }
[24] directR(R, M){ directT(T, R, M) ∣ T ∈ R.Body }
The directR of a relation is a set of RDF triples created by calling directT on each tuple in the body of the database.
  def directR (u:BaseIRI, r:Table, nodes:RowIRI, db:Database) : RDFGraph =
    body(r).flatMap(t => directT(u, t, r, nodes, db))
[25] directT(T, R, M){ directS(S, T, R, M) ∣ S = subject(T, R, M) }
The directT of a tuple in a relation is a set of RDF triples created by calling directS with an S created by the function subject.
  def directT (u:BaseIRI, t:Tuple, r:Table, nodes:RowIRI, db:Database) : Set[Triple] = {
    val s = subject(t, r, nodes, db)
    directS(u, s, t, r, nodes, db)
  }
[26] subject(T, R, M)if (pk(R) = ∅) then new blank node else rowIRI(R, T[pk(R)]) # references the ultimate referent of hierarchical key
The subject identifier for a tuple in a relation is fresh blank node, if there is no primary key, or the IRI returned from rowIRI of that primary key's attribute values in that tuple.
  def subject (t:Tuple, r:Table, nodes:RowIRI, db:Database):Node =
    if (r.candidates.size > 0) {
      // Known to have at least one key, so take the first one.
      val k = r.candidates(0)
      val vs = t.lexvaluesNoNulls(k)
      nodes.ultimateReferent(r.name, k, vs, db)
    } else
      /** Table has no candidate keys. */
      freshbnode()
[27] directS(S, T, R, M){ directL(S, R, A) ∣ A ∈ scalars(T, R) } ∪ { directN(S, As, T, M) ∣ As ∈ references(T, R) }
The directS of a subect, tuple and relation is the set of RDF triples created by:
  • calling directL on each scalar attribute in T,
  • calling directN on each foreign key in T
  def directS (u:BaseIRI, s:Node, t:Tuple, r:Table, nodes:RowIRI, db:Database) : Set[Triple] = {
    references(t, r).map(as => directN(u, s, as, r, t, nodes)) ++
    scalars(t, r).map(a => directL(u, r.name, s, a, r.header, t))
  }
[28] directL(S, R, A)triple(S, propertyIRI(R, [A]), literalmap(A))
The directL of a subject, relation and attribute is the RDF triple with that subject, the predicate returned from propertyIRI, and the object returned from literalmap.
  def directL (u:BaseIRI, rn:TableName, s:Node, a:AttrName, h:Header, t:Tuple) : Triple = {
    val p = propertyIRI (u, rn, List(a))
    val l = t.lexvalue(a).get
    val o = literalmap(l, h.sqlDatatype(a))
    Triple(s, p, o)
  }
[29] directN(S, R, As)triple(S, propertyIRI(R, As), rowIRI(R, As))
The directN of a subject, relation and list of attributes is the RDF triple with that subject, a predicate returned from propertyIRI, and the object returned by rowIRI of the list of attributes.
  def directN (u:BaseIRI, s:Node, as:List[AttrName], r:Table, t:Tuple, nodes:RowIRI) : Triple = {
    val p = propertyIRI (u, r.name, as)
    val ls:List[LexicalValue] = t.lexvaluesNoNulls(as)
    val target = r.fks(as)
    val o:Object = nodes(target.rel)(target.attrs)(ls)
    Triple(s, p, o)
  }

rowIRI generates a row IRI. propertyIRI generates a property IRI.

[31] rowIRI(R, As)IRI(UE(R.name) + "/" + (join(',', UE(A.name) + "=" + UE(A.value)) ∣ A ∈ As ) + "#_")
A rowIRI is a concatonation, with punctuation as separators, of a base IRI, url-encoded relation name, and the attribute name/value pairs in the list of attributes.
  def rowIRI (u:BaseIRI, rn:TableName, as:List[AttrName], ls:List[LexicalValue]) : IRI = {
    val pairs:List[String] = as.zip(ls).map(x => UE(x._1) + "=" + UE(x._2.s))
    u + ("/" + UE(rn) + "/" + pairs.mkString("_") + "#_")
  }
[32] propertyIRI(R, As)IRI((join(',', UE(A.name)) ∣ A ∈ As ) "#" As.name)
A propertyIRI is a concatonation, with punctuation as separators, of a base IRI, url-encoded relation name, and the attribute names the list of attributes.
  def propertyIRI (u:BaseIRI, rn:TableName, as:List[AttrName]) : IRI =
    u + ("/" + UE(rn) + "#" + as.mkString("_"))

literalmap produces RDF literal with XSD datatypes with this type mapping TM:

[40] literalmap(A)Literal(A[V], SQL2XSD[A]) ∣ SQL2XSD is the mapping from SQL datatypes to XML datatypes below:
XML Datatypes for SQL Datatypes
SQL XSD data type for typed literals, "plain literal" for plain literals
INT http://www.w3.org/TR/xmlschema-2/#integer
FLOAT http://www.w3.org/TR/xmlschema-2/#float
DATE http://www.w3.org/TR/xmlschema-2/#date
TIME http://www.w3.org/TR/xmlschema-2/#time
TIMESTAMP http://www.w3.org/TR/xmlschema-2/#dateTime
CHAR plain literal
VARCHAR plain literal
STRING plain literal

UE (url-encode) is the conventional url encoding used for e.g. HTML CGI forms:

[41] UE(T)url-encode T per WSDL urlEncoded.

4 Direct Mapping as Rules (Normative)

In this section, we formally present the Direct Mapping as rules in Datalog syntax. The left hand side of each rule is the RDF Triple output. The right hand side of each rule consists of a sequence of predicates from the relational database and built-in predicates. The built-in predicates are:

Consider again the example from Section Transformation Example. It should be noticed that in the rules presented in this section, a formula of the form Addresses(x, y, z) indicates that variables x, y and z are used to store the values of a row in the three columns of the table Addresses (according to the order specified in the schema of the table, that is, x, y and z store the values of ID, city and state, respectively). Moreover, double quotes are used in the rules to refer to the string with the name of a table or a column. For example, a formula of the form generateRowIRI("Addresses", ["ID"], [x], p) is used to generate the Row RDF Node (or Row IRI) p for the row of table "Addresses" whose value in the primary key "ID" is the value stored in the variable x.

4.1 Generating Table Triples

4.1.1 Table has a single-column primary key

Assume that r(a, b1, ..., bn) is a table with columns a, b1, ..., bn and such that [a] is the primary key of r. Then the following is the direct mapping rule to generate Table Triples from r:

Triple(s, "rdf:type", o) ← r(x, y1, ..., yn), generateRowIRI("r", ["a"], [x], s), generateTableIRI("r", o)   
		

4.1.2 Table has a multi-column primary key

Assume that r(a1, ..., am, b1, ..., bn) is a table with columns a1, ..., am, b1, ..., bn and such that [a1, ..., am] is the primary key of r (m > 1). Then the following is the direct mapping rule to generate Table Triples from r:

		
Triple(s, "rdf:type", o) ← r(x1, ..., xm, y1, ..., yn), generateRowIRI("r", ["a1", ..., "am"], [x1, ..., xm], s), generateTableIRI("r", o)
		

4.1.3 Table does not have a primary key

Assume that r(b1, ..., bn) is a table with columns b1, ..., bn and such that r does not have a primary key. Then the following is the direct mapping rule to generate Table Triples from r:

		
Triple(s, "rdf:type", o) ← r(y1, ..., yn), generateRowBlankNode("r", [y1, ..., yn], s), generateTableIRI("r", o)   
		
		

4.2 Generating Literal Triples

4.2.1 Table has a single-column primary key

Assume that r(a, b1, ..., bn) is a table with columns a, b1, ..., bn and such that [a] is the primary key of r. Then if a is not the only constituent of a foreign key of r or is the only constituent of a foreign key of r that references a candidate key, the direct mapping includes the following rule for r and a to generate Literal Triples:

		
Triple(s, p, x) ← r(x, y1, ..., yn), generateRowIRI("r", ["a"], [x], s), generateColumnIRI("r", ["a"], p)  
		
		

Moreover, for every bj (1 ≤ j ≤ n) that is not the only constituent of a foreign key of r or is the only constituent of a foreign key of r that references a candidate key, the direct mapping includes the following rule for r and bj to generate Literal Triples:

		
Triple(s, p, yj) ← r(x, y1, ..., yn), generateRowIRI("r", ["a"], [x], s), generateColumnIRI("r", ["bj"], p)  
		
		

4.2.2 Table has a multi-column primary key

Assume that r(a1, ..., am, b1, ..., bn) is a table with columns a1, ..., am, b1, ..., bn and such that [a1, ..., am] is the primary key of r (m > 1). Then for every aj (1 ≤ j ≤ m) that is not the only constituent of a foreign key of r or is the only constituent of a foreign key of r that references a candidate key, the direct mapping includes the following rule for r and aj to generate Literal Triples:

		
Triple(s, p, xj) ← r(x1, ..., xm, y1, ..., yn), generateRowIRI("r", ["a1", ..., "am"], [x1, ..., xm], s), generateColumnIRI("r", ["aj"], p)
		

Moreover, for every bj (1 ≤ j ≤ n) that is not the only constituent of a foreign key of r or is the only constituent of a foreign key of r that references a candidate key, the direct mapping includes the following rule for r and bj to generate Literal Triples:

		
Triple(s, p, yj) ← r(x1, ..., xm, y1, ..., yn), generateRowIRI("r", ["a1", ..., "am"], [x1, ..., xm], s), generateColumnIRI("r", ["bj"], p)
		

4.2.3 Table does not have a primary key

Assume that r(b1, ..., bn) is a table with columns b1, ..., bn and such that r does not have a primary key. Then for every bj (1 ≤ j ≤ n) that is not the only constituent of a foreign key of r or is the only constituent of a foreign key of r that references a candidate key, the direct mapping includes the following rule for r and bj to generate Literal Triples:

		
Triple(s, p, yj) ← r(y1, ..., yn), generateRowBlankNode("r", [y1, ..., yn], s), generateColumnIRI("r", ["bj"], p)  
		
		

4.3 Generating Reference Triples

In this section we will define the rules to generate reference triples. The different cases include when a foreign key references a single-column or multi-column primary key of another table and when a foreign key references a single-column or multi-column candidate key of another table which may or may not have a primary key.

5 References

SPARQL
SPARQL Query Language for RDF, Eric Prud'hommeaux and Andy Seaborne 2008. (See http://www.w3.org/TR/rdf-sparql-query/.)
SQLFW
SQL. ISO/IEC 9075-1:2008 SQL – Part 1: Framework (SQL/Framework) International Organization for Standardization, 27 January 2009.
SQLFN
ISO/IEC 9075-2:2008 SQL – Part 2: Foundation (SQL/Foundation) International Organization for Standardization, 27 January 2009.
RDF
Resource Description Framework (RDF): Concepts and Abstract Syntax, G. Klyne, J. J. Carroll, Editors, W3C Recommendation, 10 February 2004 (See http://www.w3.org/TR/2004/REC-rdf-concepts-20040210/.)
ReuseableIDs
Reusable Identifiers in the RDB2RDF mapping language, Michael Hausenblas and Themis Palpanas, 2009. (See http://esw.w3.org/topic/Rdb2RdfXG/ReusableIdentifier.)
URI
RFC3986 - Uniform Resource Identifier (URI): Generic Syntax (See http://tools.ietf.org/html/rfc3986.)
IRI
RFC3987 - Internationalized Resource Identifier (IRIs) (See http://tools.ietf.org/html/rfc3987.)

A CVS History

$Log: Overview.html,v $
Revision 1.4  2018/10/09 13:23:17  denis
fix validation of xhtml documents

Revision 1.3  2017/10/02 10:42:04  denis
add fixup.js to old specs

Revision 1.2  2010/11/18 10:47:45  eric
~ SOTD ammended with mhausenblas 2010-11-18T10:31:39Z
~ publication request sent

Revision 1.1  2010/11/17 22:17:24  eric
prepped for publication

Revision 1.11  2010/11/17 21:36:44  eric
~ validated HTML, CSS, links for publication

Revision 1.10  2010/11/16 17:45:35  eric
~ xml well-formed

Revision 1.9  2010/11/16 17:43:47  eric
~ 2010-11-16T17:34:38Z <ericP> mhausenblas: s/very simple direct mapping/direct mapping/
~ re-title notation
~ addressed nunolopes's issue with rule 23
~ text from #rdb2rdf 2010-11-16T17:40:18Z <juansequeda>...

Revision 1.8  2010/11/16 17:30:18  eric
~ fixed Notation title

Revision 1.7  2010/11/16 17:25:52  eric
~ re-oranized algebra section

Revision 1.6  2010/11/16 17:22:39  eric
~ re-ordered authors

Revision 1.5  2010/11/11 18:39:27  marenas
rephrasing the definition of table tuples

Revision 1.4  2010/11/11 17:58:25  marenas
New Section 2.2: "Preliminaries: Generating IRIs"
Examples are now grouped in Section 2.4: "Additional Examples and Corner Cases"

Revision 1.3  2010/11/10 14:54:48  marenas
Removing "(Editor)" from the list of authors

Revision 1.2  2010/11/10 12:56:22  eric
+ formalism-model issue

Revision 1.1  2010/11/10 02:51:03  eric
moved from ../directMapping

Revision 1.56  2010/11/10 02:47:08  eric
~ well-formedness error

Revision 1.55  2010/11/10 02:45:37  eric
~ finished adopting the all-relative-IRI model in order to sync with the merged text from alt/
~ adopted "direct" mapping per the resolution of the 2010-11-09 telecon
~ made Juan and Marcello editors instead of authors
~ fixed a couple typos
: I believe this specification follows the intent of:
  RESOLUTION: http://www.w3.org/2001/sw/rdb2rdf/directMapping/ with Juan,
  Marcelo and Eric as editors based on Richard's proposal as of
  http://lists.w3.org/Archives/Public/public-rdb2rdf-wg/2010Nov/0052.html and
  try to work in J&M's IRI and Triple generations part; move hierarchical
  table and the M-M mappings into Ed note; datalog as a separate section; Eric
  perform merge with review/approval/consensus of Juan, Marcelo, & Eric

Revision 1.54  2010/11/09 22:46:56  eric
+ hier-table-at-risk issue

Revision 1.53  2010/11/09 22:41:02  eric
~ date

Revision 1.52  2010/11/09 22:39:12  eric
~ s/stem/base/g
+ inclusion of collapsible sections from alt/

Revision 1.51  2010/11/09 15:39:46  eric
~ removed collapsible sections per request mid:AANLkTikvnrgXuu5fDAw+c2nUv5ENkmngPAJJ05c2gASk@mail.gmail.com

Revision 1.50  2010/11/09 15:06:34  eric
+ exp sections

Revision 1.49  2010/11/09 14:12:08  eric
~ addressed cygri's comments of 2010-11-09T06:46:12Z

Revision 1.48  2010/11/09 04:11:35  eric
~ addressed cygri's comments of 2010-11-07T04:13Z
+ inclusion of some explanatory details from alt

Revision 1.47  2010/11/04 12:42:21  eric
~ working on style for editorial choices

Revision 1.46  2010/11/04 06:10:08  eric
~ hilit triples in query in Use of Direct Mapping

Revision 1.45  2010/11/04 05:42:55  eric
~ incorporated Richard Cyganiak's comments targeted at alt

Revision 1.44  2010/11/02 08:18:07  eric
~ updates per DanC's feedback

Revision 1.43  2010/10/29 03:10:12  eric
~ s/relational terminology/SQL terminology/

Revision 1.42  2010/10/17 13:46:48  eric
+ SQL constraints

Revision 1.41  2010/10/12 14:21:36  eric
~ renumbered

Revision 1.40  2010/10/12 12:14:52  eric
+ SQL for example 1

Revision 1.39  2010/10/11 03:12:21  eric
~ prettied up mutual-hilights

Revision 1.38  2010/10/10 22:09:55  eric
+ pfkexception

Revision 1.37  2010/10/10 14:25:41  eric
~ re-worked front-loaded informative rules

Revision 1.36  2010/10/10 11:59:01  eric
~ prettied-up pre@class=turtle
~ experimenting with new presentation of transformation rules
~ validated XSLT output

Revision 1.35  2010/10/09 15:12:40  eric
+ crosslinks for hier-tabl

Revision 1.34  2010/10/09 14:52:31  eric
+ crosslinks for ref-no-pk

Revision 1.33  2010/10/09 13:45:17  eric
~ symmetric xrefs between tables and triples for emp-addr and multi-key

Revision 1.32  2010/10/08 21:59:54  eric
+ hilights

Revision 1.31  2010/09/29 19:53:37  eric
~ align with https://dvcs.w3.org/hg/stemGraph/

Revision 1.30  2010/09/29 15:13:18  eric
~ align with https://dvcs.w3.org/hg/stemGraph/rev/75cf39ef7d74

Revision 1.29  2010/09/29 03:34:55  eric
+ 2nd gen hierarchical example

Revision 1.28  2010/09/28 03:10:53  eric
validation

Revision 1.27  2010/09/28 03:08:52  eric
+ hierarchical (untested)

Revision 1.26  2010/09/27 21:49:18  eric
~ XML validation (per xsltproc)

Revision 1.25  2010/09/27 21:46:42  eric
~ fixed reference table name

Revision 1.24  2010/09/27 18:48:46  eric
+ noticed another key in ref-no-pk

Revision 1.23  2010/09/27 18:13:03  eric
+ ref-no-pk

Revision 1.22  2010/09/27 14:50:44  eric
+ nodemap
+ a rough pass on <scala>scala code</scala>

Revision 1.21  2010/09/26 04:50:07  eric
~ fix load state for syntax display

Revision 1.20  2010/09/25 18:40:39  eric
+ some tips

Revision 1.19  2010/09/24 16:34:02  eric
+ some tips

Revision 1.18  2010/09/24 16:00:53  eric
+ some tips

Revision 1.17  2010/09/24 15:50:41  eric
+ buttons for different languages

Revision 1.16  2010/09/07 12:14:44  eric
~ fixed pk invocation errors per  mid:04C1B62C-42A5-424C-974B-6E894ED7B11A@cyganiak.de

Revision 1.15  2010/08/30 18:37:19  eric
+ Empty (Non-existent) Primary Keys section

Revision 1.14  2010/08/30 14:05:45  eric
+ fks

Revision 1.13  2010/08/30 13:27:01  eric
~ content-free prettying-up

Revision 1.12  2010/08/23 14:24:22  eric
~ simplified per Richard's suggestions

Revision 1.11  2010/07/18 21:33:21  eric
~ proof-reading for an explanatory email

Revision 1.10  2010/07/12 23:15:42  eric
~ typos revealed when geeking with Sandro

Revision 1.9  2010/06/15 15:23:43  eric
~ validating

Revision 1.8  2010/06/15 14:33:59  eric
~ s/Heading/Header/ per google fight

Revision 1.7  2010/06/15 14:27:08  eric
~ s/∀/∣/g per cygri's comment

Revision 1.6  2010/06/07 15:49:34  eric
+ Extending the Direct Mapping section

Revision 1.5  2010/06/07 14:35:18  eric
+ finished mappings

Revision 1.4  2010/06/03 23:02:58  eric
~ SNAPSHOT

Revision 1.3  2010/06/03 13:06:00  eric
+ start on literal map

Revision 1.2  2010/06/03 12:43:30  eric
~ made algebra all symboly

Revision 1.1  2010/06/02 15:03:34  eric
CREATED