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):
'http://leifhka.org/lore/library/prefix.lore';
IMPORT
'xsd', 'http://www.w3.org/2001/XMLSchema#');
prefix(CREATE SCHEMA IF NOT EXISTS xsd;
'rdfs', 'http://www.w3.org/2000/01/rdf-schema#');
prefix(CREATE SCHEMA IF NOT EXISTS rdfs;
'ex', 'http://example.org/');
prefix(CREATE SCHEMA IF NOT EXISTS ex;
'rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
prefix(CREATE SCHEMA IF NOT EXISTS rdf;
'owl', 'http://www.w3.org/2002/07/owl#');
prefix(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.Person(x);
ex.Employee(x) -> ex.Person(x);
ex.Student(x)
-> ex.parent(x, y);
ex.mother(x, y)
parent(x, y) -> ex.Person(y);
ex.parent(x, y) -> ex.Person(x);
ex.-> ex.Person(x);
ex.name(x, y) -> ex.Person(x);
ex.age(x, y)
Class('http://example.org/Student');
owl.Class('http://example.org/Employee');
owl.Class('http://example.org/Person');
owl.'http://example.org/name');
owl.DatatypeProperty('http://example.org/age');
owl.DatatypeProperty('http://example.org/mother');
owl.ObjectProperty('http://example.org/parent');
owl.ObjectProperty(
'http://example.org/ola');
ex.Student('http://example.org/ola', 'Ola');
ex.name('http://example.org/kari', 'http://example.org/ola');
ex.mother('http://example.org/kari', '35'); ex.age(
Supported Semantics: Lore-OWL
- Triplelore supports a subset of OWL 2 RL called Lore-OWL
- Triplelore translates:
- every class into a unary relation
- every property into a binary relation
- every statement
x rdf:type A
intoA(x)
- every (other) triple
s p o
intop(s, o)
- Each prefix is translated into a schema
- E.g.
ex:A
can then be used asex.A
- Prefixes used are added to a special table
(
prefix
) - URIs/IRIs within the classes and properties are written out in full
- Can use
qn('ex', 'ole')
forex:ole
- E.g.
- The semantics is translated into rules
- E.g.
ex:A rdfs:subClassOf ex:B
becomesex.A(x) -> ex.B(x)
- Can specify whether should use forward or backwards chaining
(
-b
flag)
- E.g.
Supports:
- all of RDFS (
rdf:type
,rdfs:subClassOf
,rdfs:subPropertyOf
,rdfs:range
,rdfs:domain
) - class intersection
- class union (for subclasses)
- Reflexive properties:
owl:ReflexiveProperty
- Transitive properties:
owl:TransitiveProperty
- Symmetric properties:
owl:SymmetricProperty
- Equivalent properties:
owl:equivalentProperty
- Property chains
Note:
- All properties needs to be typed as either
owl:ObjectProperty
orowl:DatatypeProperty
- All
owl:DatatypeProperty
s needs to have anrdfs:range
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);
'ex.Employee');
triplelore_class('owl.DatatypeProperty');
triplelore_class('owl.Class');
triplelore_class('ex.Person');
triplelore_class('ex.Student');
triplelore_class('owl.ObjectProperty');
triplelore_class('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'); triplelore_property(
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:
- To directly map a set of regular tables, you need to add a row to
the relation
mappings.direct_mapping
: The first column contains the schame name of the relation to map; the second column contains the table name, the third contains an IRI to use as base-IRI for the generated IRIs of resources, classes and properties; the fourth element contains a short-name that will be a shortname for the prefix resulting from appending the schema name to the end of the base-IRI; and finally, a boolean stating whether or not the mappings should be constructed using forward chaining rules or not. - To directly map a Lore-relation or view, you need to make a
corresponding row in
mappings.direct_mapping
, and in adition you need to specify any implicit primary key inmappings.primary_key
and any foreign key inmappings.foreign_key
. The relationmappings.primary_key
simply takes the schema name, relation/view name and column name of the primary key (multi-column primary keys simply use multiple rows); whereasmappings.foreing_key
consists of a constraint name (one can make up any string as long as it is unique accross foreign keys and equal accross rows describing columns in the same multi-column foreign key), the schema, relation/view name and column name of the referencing columns, and finally the schema, relation/view/table name and column of the referenced column.
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:
'http://leifhka.org/lore/library/direct_mappings.lore';
IMPORT
'university', 'employee', 'http://example.org/', 'uni', true);
mappings.direct_mapping('university', 'student', 'http://example.org/', 'uni', false); mappings.direct_mapping(
the following Lore-script is made:
'https://leifhka.org/lore/library/prefix.lore';
IMPORT 'https://leifhka.org/lore/library/common_prefixes.lore';
IMPORT 'https://leifhka.org/lore/library/triplelore.lore';
IMPORT
CREATE SCHEMA IF NOT EXISTS uni;
'uni', 'http://example.org/university/');
prefix(
CREATE RELATION uni.employee(individual text);
'uni.employee');
triplelore_class(CREATE RELATION uni.employee_id(subject text, object integer);
'uni.employee_id', 'http://www.w3.org/2001/XMLSchema#int');
triplelore_property(CREATE RELATION uni.employee_name(subject text, object text);
'uni.employee_name', 'http://www.w3.org/2001/XMLSchema#string');
triplelore_property(CREATE RELATION uni.employee_email(subject text, object text);
'uni.employee_email', 'http://www.w3.org/2001/XMLSchema#string');
triplelore_property(CREATE RELATION uni.student(individual text);
'uni.student');
triplelore_class(CREATE RELATION uni.student_ref_supervisor(subject text, object text);
'uni.student_ref_supervisor', null);
triplelore_property(CREATE RELATION uni.student_id(subject text, object integer);
'uni.student_id', 'http://www.w3.org/2001/XMLSchema#int');
triplelore_property(CREATE RELATION uni.student_name(subject text, object text);
'uni.student_name', 'http://www.w3.org/2001/XMLSchema#string');
triplelore_property(CREATE RELATION uni.student_supervisor(subject text, object integer);
'uni.student_supervisor', 'http://www.w3.org/2001/XMLSchema#int');
triplelore_property(
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:
'http://leifhka.org/lore/library/direct_mappings.lore';
IMPORT
'university', 'employee', 'http://example.org/', 'uni', true);
mappings.direct_mapping('university', 'student', 'http://example.org/', 'uni', true);
mappings.direct_mapping(
'university', 'employee', 'id');
mappings.primary_key('university', 'student', 'id');
mappings.primary_key(
'1', 'university', 'student', 'supervisor', 'university', 'employee', 'id'); mappings.foreign_key(