TripleLore

TripleLore is a triplestore and ontology reasoner built using Lore. TripleLore translates an RDF-graph into a Lore-script that creates a database schema with a unary relation for each class, a binary relation for each property, and implications for handling reasoning.

The newest release of TripleLore can be downloaded here. The source code is published on Gitlab under GPL, written in Scala, and is available here.

Example

OWL/RDF:

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ex: <http://example.org/> .

ex:Person a owl:Class .

ex:Student a owl:Class ;
    rdfs:subClassOf ex:Person .

ex:Employee a owl:Class ;
    rdfs:subClassOf ex:Person .

ex:parent a owl:ObjectProperty ;
    rdfs:range ex:Person ;
    rdfs:domain ex:Person .
    
ex:mother a owl:ObjectProperty ;
    rdfs:subPropertyOf ex:parent .

ex:name a owl:DatatypeProperty ;
    rdfs:domain ex:Person ;
    rdfs:range xsd:string .

ex:age a owl:DatatypeProperty ;
    rdfs:domain ex:Person ;
    rdfs:range xsd:integer .

ex:ola a ex:Student ;
    ex:name "Ola" .

ex:kari ex:mother ex:ola ;
    ex:age "35"^^xsd:integer .

Translated Lore (simplified):

IMPORT 'http://leifhka.org/lore/library/prefix.lore';

prefix('xsd', 'http://www.w3.org/2001/XMLSchema#');
CREATE SCHEMA IF NOT EXISTS xsd;
prefix('rdfs', 'http://www.w3.org/2000/01/rdf-schema#');
CREATE SCHEMA IF NOT EXISTS rdfs;
prefix('ex', 'http://example.org/');
CREATE SCHEMA IF NOT EXISTS ex;
prefix('rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
CREATE SCHEMA IF NOT EXISTS rdf;
prefix('owl', 'http://www.w3.org/2002/07/owl#');
CREATE SCHEMA IF NOT EXISTS owl;

CREATE RELATION owl.Class(individual text);
CREATE RELATION owl.DatatypeProperty(individual text);
CREATE RELATION owl.ObjectProperty(individual text);

CREATE RELATION ex.Employee(individual text);
CREATE RELATION ex.Person(individual text);
CREATE RELATION ex.Student(individual text);

CREATE RELATION ex.mother(subject text, object text);
CREATE RELATION ex.parent(subject text, object text);
CREATE RELATION ex.name(subject text, object text);
CREATE RELATION ex.age(subject text, object bigint);

ex.Employee(x) -> ex.Person(x);
ex.Student(x) -> ex.Person(x);

ex.mother(x, y) -> ex.parent(x, y);

ex.parent(x, y) -> ex.Person(y);
ex.parent(x, y) -> ex.Person(x);
ex.name(x, y) -> ex.Person(x);
ex.age(x, y) -> ex.Person(x);

owl.Class('http://example.org/Student');
owl.Class('http://example.org/Employee');
owl.Class('http://example.org/Person');
owl.DatatypeProperty('http://example.org/name');
owl.DatatypeProperty('http://example.org/age');
owl.ObjectProperty('http://example.org/mother');
owl.ObjectProperty('http://example.org/parent');

ex.Student('http://example.org/ola');
ex.name('http://example.org/ola', 'Ola');
ex.mother('http://example.org/kari', 'http://example.org/ola');
ex.age('http://example.org/kari', '35');

Supported Semantics: Lore-OWL

Supports:

Note:

Below is a table showing the translation of OWL-axioms into rules:

OWL/RDF Lore-rule
?A rdfs:subClassOf ?B . ?A(x) -> ?B(x);
?R rdfs:subPropertyOf ?P . ?R(x,y) -> ?P(x,y);
?R rdfs:domain ?C . ?R(x,y) -> ?C(x);
?R rdfs:range ?C . ?R(x,y) -> ?C(y);
?R owl:equivalentProperty ?P . ?R(x,y) -> ?P(x,y); and ?P(x,y) -> ?R(x,y);
?P owl:inverseOf ?R . ?P(x,y) -> ?R(y,x); and ?R(x,y) -> ?P(y,x);
?R rdf:type owl:ReflexiveProperty ; rdfs:domain ?C . ?C(x) -> ?R(x, x);
?R a owl:SymmetricProperty . ?R(x,y) -> ?R(y,x);
?R a owl:TransitiveProperty . ?R(x,y), ?R(y,z) -> ?R(x,z);
?C rdfs:subClassOf [ owl:intersectionOf (?C1 ?C2 ... ?Ci ... ?Cn) ] . ?C(x) -> ?Ci(x); for each 1 <= i <= n
[ owl:intersectionOf (?C1 ?C2 ... ?Cn) ] rdfs:subClassOf ?C . ?C1(x), ?C2(x), ..., ?Cn(x) -> ?Cx);
[ owl:unionOf (?C1 ?C2 ... ?Ci ... ?Cn); rdfs:subClassOf ?C ] . ?Ci(x) -> ?C(x); for each 1 <= i <= n
?R owl:propertyChainAxiom (?P1 ?P2 ... ?Pn) . ?P1(x,x2), ?P2(x2,x3), ..., ?Pn(xn,y) -> ?R(x,y);

where none of the relations (i.e. ?A, ?B, ?R, ?P, etc.) can be blank nodes. The IRIs are translated into relation names as described above in the rules (e.g. ex:R becomes ex.R).

SPARQL over Triplelore

The resulting tables can be mapped to RDF via R2RML and then queried via SPARQL with a third-party R2RML-processor (such as Ontop). To enable the generation of R2RML mappings, aditional info is also stored in the DB.

E.g. these are the mappings added for the triples from previous example:

CREATE RELATION triplelore_class(cname text);
CREATE RELATION triplelore_property(pname text, range text);

triplelore_class('ex.Employee');
triplelore_class('owl.DatatypeProperty');
triplelore_class('owl.Class');
triplelore_class('ex.Person');
triplelore_class('ex.Student');
triplelore_class('owl.ObjectProperty');
triplelore_property('ex.mother', NULL);
triplelore_property('ex.parent', NULL);
triplelore_property('ex.name', 'http://www.w3.org/2001/XMLSchema#string');
triplelore_property('ex.age', 'http://www.w3.org/2001/XMLSchema#integer');

We can now call TripleLore with -m r2rml -o <mappingsfile> to generate the mappings. E.g.:

java -jar triplelore.jar \
    -m r2rml \
    -h dbpg-ifi-kurs03 \
    -U leifhka \
    -d in5800 \
    -P pwd123 \
    -o mappings.ttl

These mappings can then be given to e.g. Ontop to query the resulting triplestore with SPARQL, e.g.:

ontop query \
    --db-driver=org.postgresql.Driver \
    --db-url=jdbc:postgresql://dbpg-ifi-kurs03.uio.no/in5800 \
    --db-user=leifhka \
    --db-password=pwd123 \
    -m mappings.ttl
    -q query.sparql

assuming the same database as above, mappings.ttl is the mappingsfile generated by the above command, and where the SPARQL-query is located in query.sparql.

Direct Mappings

Triplelore supports direct mappings from Lore-relations, or regular tables or views, to the triple/OWL-structure described above. The mappings adhere to the Direct Mappings W3C-standard, except that the IRIs are changed to fit with the encoding of classes and properties as described above.

The Lore-script located at https://leifhka.org/lore/library/direct_mappings.lore contains a set of relations for telling Triplelore which things to directly map. To make direct mappings, one therefore needs to make a Lore-script importing the above file, and then add the information described below:

Once a file inserting the desired mapping information is made, one needs to execute it in the database with Lore, then execute Triplelore with the -m directmappings mode and an output file (e.g. -o mapping_rules.lore), and finally, execute the resulting mapping rules over the database with Lore.

Example

Given the following tables:

university.employee

 id |    name    |        email        
----+------------+---------------------
  0 | Mary Smith | mary@smith.no
  1 | Carl Green | carl_green@mail.net

university.student

 id |    name     | supervisor 
----+-------------+------------
  0 | Peter Swan  |           
  1 | Helen Brown |          0
  2 | Nathan Case |          1

where the id-column of each table is the table’s primary key, and supervisor is a foreign key to university.employee(id), and the following Lore-script describing the mappings:

IMPORT 'http://leifhka.org/lore/library/direct_mappings.lore';

mappings.direct_mapping('university', 'employee', 'http://example.org/', 'uni', true);
mappings.direct_mapping('university', 'student', 'http://example.org/', 'uni', false);

the following Lore-script is made:

IMPORT 'https://leifhka.org/lore/library/prefix.lore';
IMPORT 'https://leifhka.org/lore/library/common_prefixes.lore';
IMPORT 'https://leifhka.org/lore/library/triplelore.lore';

CREATE SCHEMA IF NOT EXISTS uni;
prefix('uni', 'http://example.org/university/');

CREATE RELATION uni.employee(individual text);
triplelore_class('uni.employee');
CREATE RELATION uni.employee_id(subject text, object integer);
triplelore_property('uni.employee_id', 'http://www.w3.org/2001/XMLSchema#int');
CREATE RELATION uni.employee_name(subject text, object text);
triplelore_property('uni.employee_name', 'http://www.w3.org/2001/XMLSchema#string');
CREATE RELATION uni.employee_email(subject text, object text);
triplelore_property('uni.employee_email', 'http://www.w3.org/2001/XMLSchema#string');
CREATE RELATION uni.student(individual text);
triplelore_class('uni.student');
CREATE RELATION uni.student_ref_supervisor(subject text, object text);
triplelore_property('uni.student_ref_supervisor', null);
CREATE RELATION uni.student_id(subject text, object integer);
triplelore_property('uni.student_id', 'http://www.w3.org/2001/XMLSchema#int');
CREATE RELATION uni.student_name(subject text, object text);
triplelore_property('uni.student_name', 'http://www.w3.org/2001/XMLSchema#string');
CREATE RELATION uni.student_supervisor(subject text, object integer);
triplelore_property('uni.student_supervisor', 'http://www.w3.org/2001/XMLSchema#int');

CREATE FORWARD IMPLICATION uni.employee AS
SELECT 'http://example.org/university/employee/' || 'id=' || id::text FROM university.employee AS t;
CREATE FORWARD IMPLICATION uni.employee_id AS
SELECT 'http://example.org/university/employee/' || 'id=' || id::text, id FROM university.employee AS t
WHERE id IS NOT NULL;
CREATE FORWARD IMPLICATION uni.employee_name AS
SELECT 'http://example.org/university/employee/' || 'id=' || id::text, name FROM university.employee AS t
WHERE name IS NOT NULL;
CREATE FORWARD IMPLICATION uni.employee_email AS
SELECT 'http://example.org/university/employee/' || 'id=' || id::text, email FROM university.employee AS t
WHERE email IS NOT NULL;
CREATE IMPLICATION uni.student AS
SELECT 'http://example.org/university/student/' || 'id=' || id::text FROM university.student AS t;
CREATE IMPLICATION uni.student_id AS
SELECT 'http://example.org/university/student/' || 'id=' || id::text, id FROM university.student AS t
WHERE id IS NOT NULL;
CREATE IMPLICATION uni.student_name AS
SELECT 'http://example.org/university/student/' || 'id=' || id::text, name FROM university.student AS t
WHERE name IS NOT NULL;
CREATE IMPLICATION uni.student_supervisor AS
SELECT 'http://example.org/university/student/' || 'id=' || id::text, supervisor FROM university.student AS t
WHERE supervisor IS NOT NULL;
CREATE IMPLICATION uni.student_ref_supervisor AS
SELECT 'http://example.org/university/student/' || 'id=' || id::text, 'http://example.org/university/employee/' || 'id=' || supervisor::text FROM university.student AS t
WHERE supervisor IS NOT NULL;

Executing these with Lore will result in the follwing triples being represented in the DB:

@prefix uni: <http://example.org/university/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<http://example.org/university/student/id=2>
    a                           unis:student ;
    uni:student_id              "2"^^xsd:int ;
    uni:student_name            "Nathan Case" ;
    uni:student_ref_supervisor  <http://example.org/university/employee/id=1> ;
    uni:student_supervisor      "1"^^xsd:int .

<http://example.org/university/employee/id=0>
    a                   uni:employee ;
    uni:employee_email  "mary@smith.no" ;
    uni:employee_id     "0"^^xsd:int ;
    uni:employee_name   "Mary Smith" .

<http://example.org/university/student/id=0>
    a                 uni:student ;
    uni:student_id    "0"^^xsd:int ;
    uni:student_name  "Peter Swan" .

<http://example.org/university/employee/id=1>
    a                   uni:employee ;
    uni:employee_email  "carl_green@mail.net" ;
    uni:employee_id     "1"^^xsd:int ;
    uni:employee_name   "Carl Green" .

<http://example.org/university/student/id=1>
    a                           uni:student ;
    uni:student_id              "1"^^xsd:int ;
    uni:student_name            "Helen Brown" ;
    uni:student_ref_supervisor  <http://example.org/university/employee/id=0> ;
    uni:student_supervisor      "0"^^xsd:int .

The command-line invocations that needs to be done, assuming the mappings are described in the file mappings.lore:

java -jar lore.jar <flags> mappings.lore
java -jar triplelore.jar <flags> -m directmappings -o mapping_rules.lore
java -jar lore.jar <flags> mapping_rules.lore

where <flags> are the database connection flags.

If the two tables mapped were Lore-relations or views, but with the same implicit keys, mappings.lore shoul rather look like this:

IMPORT 'http://leifhka.org/lore/library/direct_mappings.lore';

mappings.direct_mapping('university', 'employee', 'http://example.org/', 'uni', true);
mappings.direct_mapping('university', 'student', 'http://example.org/', 'uni', true);

mappings.primary_key('university', 'employee', 'id');
mappings.primary_key('university', 'student', 'id');

mappings.foreign_key('1', 'university', 'student', 'supervisor', 'university', 'employee', 'id');