Ontologies¶
An ontology is a formal model of a domain: the classes of things that exist, the properties that describe and relate them, and (optionally) the individuals — the actual instances. The web standard for ontologies is OWL, built on RDF; an ontology is just a set of RDF triples and can be serialized as RDF/XML (.owl), Turtle (.ttl), N-Triples, JSON-LD, and more.
Ontologies map almost one-to-one onto Synalog's knowledge-graph conventions: a class is a node, a relationship is an edge, an individual is a fact. synalog import does that translation for you, so an ontology someone already modeled — yours, a domain standard, or one downloaded from the web — becomes a runnable Synalog program with no hand-writing.
Importing an ontology¶
synalog import reads any RDF serialization rdflib understands (.owl, .ttl, .rdf, .n3, JSON-LD), from a local file or an http(s)/ftp URL, and writes a Synalog program to stdout — redirect it into a .l file:
synalog import ontology.ttl > ontology.l # local file
synalog import https://example.org/onto.owl > onto.l # downloaded
The output has two layers, so the command works whether an ontology is mostly a schema (lots of classes, few or no individuals — a taxonomy like the OBO biomedical ontologies) or mostly data (rich in individuals — FOAF, the HR model below):
Schema layer (always emitted — the TBox):
| Ontology construct | Synalog output |
|---|---|
| Class | a row of Class (keyed by the class URI, with a label from rdfs:label) |
rdfs:subClassOf |
a recursive SubClassOf — the transitive closure of the hierarchy |
Instance layer (emitted for each class that actually has individuals — the ABox):
| Ontology construct | Synalog output |
|---|---|
| Class with individuals | a concept (named after the class) keyed by the individual's URI (preserved verbatim) |
| Datatype property | a column on the concept of every individual that uses it |
| Object property | a concept (named after the property) joining two entities through their URIs |
| Individual | a *Raw fact the concepts build on, so the program runs as-is |
OWL property axioms and characteristics are translated to the matching rule patterns — so a transitive property is actually closed, an inverse is actually derived, and a functional property is actually checked. Rather than tabulate them, Every OWL axiom, by example below shows each construct end-to-end: the OWL declaration and the exact Synalog import generates from it.
Characteristics propagate across owl:inverseOf and owl:equivalentProperty (the inverse of a transitive property is transitive, of a functional one is inverse-functional; equivalents share everything), so the closures are complete on both sides. Every generated program is validated by the verifier. owl:complementOf and owl:propertyChainAxiom are not translated — they need open-world / non-stratifiable reasoning that does not map to a finite SQL rule.
Emitting per-class entity concepts only for classes that have individuals is what keeps a class-centric ontology usable: a 15,000-class taxonomy with no individuals becomes a single Class plus its SubClassOf hierarchy, not 15,000 empty one-row predicates.
Datatype columns are data-driven: a property becomes a column of a concept when an individual of that class actually uses it. That means inheritance works without reasoning over the class hierarchy — an individual typed as a subclass still gets the superclass's properties — and properties with no declared rdfs:domain are picked up too. Values keep their natural type (numbers, booleans) where rdflib can infer it; strings are emitted as single-quoted literals, and a property an individual lacks is filled with null so every row of a concept has the same shape.
Building an ontology with Protégé¶
Protégé is the standard free, open-source ontology editor from Stanford. It's the easiest way to author an ontology by hand and export a file synalog import can read.
-
Install and create. Download the desktop app from protege.stanford.edu, then File → New. On the Active Ontology tab, set the Ontology IRI to a base IRI for your domain (e.g.
http://example.org/hr#); every term you create lives under it, and these IRIs become theurivalues in the generated program.
-
Classes (the Classes / Entities tab) — add a class per kind of entity (
Person,Company, …). Nest a class under another to create anrdfs:subClassOfrelationship (EmployeeunderPerson); Synalog turns the hierarchy intoSubClassOf. In the screenshot below the class hierarchy is on the left and the selected class's superclasses are in Class Description on the right.
-
Datatype properties (Data properties tab) — add the attributes (
name,email,salary). Set each property's Domain to the class it describes and its Range to a datatype (xsd:string,xsd:integer, …). These become node columns. - Object properties (Object properties tab) — add the relationships (
worksAt,reportsTo,knows). Set Domain and Range to the connected classes; these become relationship concepts that join through the two classes. - Individuals (Individuals tab, optional) — add instances, assign each a Type (its class), and fill in property values. These become the
*Rawfacts, so the imported program runs immediately. Skip this step to import a schema-only ontology (the concepts are still generated, just without data). -
Export. File → Save as… and choose RDF/XML (
.owl) or Turtle (.ttl) —synalog importreads either. Then:
Don't build from scratch when a standard exists
Many domains already have a public ontology (schema.org, FOAF, SKOS, the OBO biomedical ontologies, Wikidata, …). Point synalog import straight at its URL, or open it in Protégé to adapt it before importing.
Screenshots from the Protégé "Getting Started" guide (Stanford Center for Biomedical Informatics Research), using the example Pizza ontology.
Worked example¶
This small HR ontology (Turtle) has three levels of classes, a few datatype and object properties, and four individuals:
# A small self-contained ontology (Turtle): a TBox (classes + properties) and
# an ABox (individuals). `synalog import ontology.ttl > ontology.l` converts it
# to the Synalog program in ontology.l.
@prefix : <http://example.org/hr#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
# Classes
:Person a owl:Class .
:Employee a owl:Class ; rdfs:subClassOf :Person .
:Manager a owl:Class ; rdfs:subClassOf :Employee .
:Company a owl:Class .
# Datatype properties (become node columns)
:name a owl:DatatypeProperty ; rdfs:domain :Person ; rdfs:range xsd:string .
:email a owl:DatatypeProperty ; rdfs:domain :Person ; rdfs:range xsd:string .
:salary a owl:DatatypeProperty ; rdfs:domain :Employee ; rdfs:range xsd:integer .
# Object properties (become edges)
:worksAt a owl:ObjectProperty ; rdfs:domain :Employee ; rdfs:range :Company .
:reportsTo a owl:ObjectProperty ; rdfs:domain :Employee ; rdfs:range :Manager .
:knows a owl:ObjectProperty ; rdfs:domain :Person ; rdfs:range :Person .
# Individuals (the ABox)
:acme a :Company ; :name "Acme \"Robotics\"" .
:carol a :Manager ; :name "Carol" ; :email "carol@acme.example" ; :salary 150000 ;
:worksAt :acme .
:alice a :Employee ; :name "Alice O'Hara" ; :email "alice@acme.example" ; :salary 90000 ;
:worksAt :acme ; :reportsTo :carol ; :knows :bob .
:bob a :Person ; :name "Bob" ; :knows :alice .
The same ontology as RDF/XML (ontology.owl)
The serialization doesn't matter — synalog import ontology.owl converts this RDF/XML form (the default Protégé export) to exactly the same program.
<?xml version="1.0" encoding="utf-8"?>
<!-- The same ontology as ontology.ttl, serialized as RDF/XML (.owl).
`synalog import ontology.owl` converts it identically. -->
<rdf:RDF
xmlns:hr="http://example.org/hr#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
>
<owl:DatatypeProperty rdf:about="http://example.org/hr#email">
<rdfs:domain rdf:resource="http://example.org/hr#Person"/>
<rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#string"/>
</owl:DatatypeProperty>
<owl:ObjectProperty rdf:about="http://example.org/hr#reportsTo">
<rdfs:domain rdf:resource="http://example.org/hr#Employee"/>
<rdfs:range rdf:resource="http://example.org/hr#Manager"/>
</owl:ObjectProperty>
<owl:DatatypeProperty rdf:about="http://example.org/hr#name">
<rdfs:domain rdf:resource="http://example.org/hr#Person"/>
<rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#string"/>
</owl:DatatypeProperty>
<owl:ObjectProperty rdf:about="http://example.org/hr#knows">
<rdfs:domain rdf:resource="http://example.org/hr#Person"/>
<rdfs:range rdf:resource="http://example.org/hr#Person"/>
</owl:ObjectProperty>
<owl:ObjectProperty rdf:about="http://example.org/hr#worksAt">
<rdfs:domain rdf:resource="http://example.org/hr#Employee"/>
<rdfs:range rdf:resource="http://example.org/hr#Company"/>
</owl:ObjectProperty>
<owl:DatatypeProperty rdf:about="http://example.org/hr#salary">
<rdfs:domain rdf:resource="http://example.org/hr#Employee"/>
<rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#integer"/>
</owl:DatatypeProperty>
<owl:Class rdf:about="http://example.org/hr#Company"/>
<hr:Employee rdf:about="http://example.org/hr#alice">
<hr:name>Alice O'Hara</hr:name>
<hr:email>alice@acme.example</hr:email>
<hr:salary rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">90000</hr:salary>
<hr:worksAt>
<hr:Company rdf:about="http://example.org/hr#acme">
<hr:name>Acme "Robotics"</hr:name>
</hr:Company>
</hr:worksAt>
<hr:reportsTo>
<hr:Manager rdf:about="http://example.org/hr#carol">
<hr:name>Carol</hr:name>
<hr:email>carol@acme.example</hr:email>
<hr:salary rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">150000</hr:salary>
<hr:worksAt rdf:resource="http://example.org/hr#acme"/>
</hr:Manager>
</hr:reportsTo>
<hr:knows>
<hr:Person rdf:about="http://example.org/hr#bob">
<hr:name>Bob</hr:name>
<hr:knows rdf:resource="http://example.org/hr#alice"/>
</hr:Person>
</hr:knows>
</hr:Employee>
<owl:Class rdf:about="http://example.org/hr#Person"/>
<owl:Class rdf:about="http://example.org/hr#Employee">
<rdfs:subClassOf rdf:resource="http://example.org/hr#Person"/>
</owl:Class>
<owl:Class rdf:about="http://example.org/hr#Manager">
<rdfs:subClassOf rdf:resource="http://example.org/hr#Employee"/>
</owl:Class>
</rdf:RDF>
synalog import ontology.ttl produces the program below. Note how Employee and Manager carry name/email even though those properties are declared on Person — the columns follow the data:
# run: Class, SubClassOf, Person, Employee, Manager, Company, WorksAt, Knows, ReportsTo
# Generated by `synalog import` from an OWL/RDF ontology.
# 4 classes, 3 object properties, 4 individuals.
# Schema — every class as a concept (the TBox)
@OrderBy(Class, "uri");
Class(uri:) distinct :- ClassRaw(uri:);
ClassRaw(uri: 'http://example.org/hr#Company');
ClassRaw(uri: 'http://example.org/hr#Employee');
ClassRaw(uri: 'http://example.org/hr#Manager');
ClassRaw(uri: 'http://example.org/hr#Person');
# Class hierarchy (transitive subClassOf / equivalentClass), joined through Class
@Recursive(SubClassOf, 100);
SubClassOf(child_uri:, parent_uri:) distinct :- SubClassOfRaw(child_uri:, parent_uri:), Class(uri: child_uri), Class(uri: parent_uri);
SubClassOf(child_uri:, parent_uri:) distinct :- SubClassOf(child_uri:, parent_uri: mid), SubClassOfRaw(child_uri: mid, parent_uri:);
SubClassOfRaw(child_uri: 'http://example.org/hr#Employee', parent_uri: 'http://example.org/hr#Person');
SubClassOfRaw(child_uri: 'http://example.org/hr#Manager', parent_uri: 'http://example.org/hr#Employee');
# Concepts — classes that have individuals
@OrderBy(Company, "uri");
Company(uri:, name:) distinct :- CompanyRaw(uri:, name:);
CompanyRaw(uri: 'http://example.org/hr#acme', name: 'Acme "Robotics"');
@OrderBy(Employee, "uri");
Employee(uri:, email:, name:, salary:) distinct :- EmployeeRaw(uri:, email:, name:, salary:);
EmployeeRaw(uri: 'http://example.org/hr#alice', email: 'alice@acme.example', name: 'Alice O\'Hara', salary: 90000);
@OrderBy(Manager, "uri");
Manager(uri:, email:, name:, salary:) distinct :- ManagerRaw(uri:, email:, name:, salary:);
ManagerRaw(uri: 'http://example.org/hr#carol', email: 'carol@acme.example', name: 'Carol', salary: 150000);
@OrderBy(Person, "uri");
Person(uri:, email:, name:) distinct :- PersonRaw(uri:, email:, name:);
PersonRaw(uri: 'http://example.org/hr#bob', email: null, name: 'Bob');
# Concepts — object properties (relationships)
@OrderBy(Knows, "subject_uri");
Knows(subject_uri:, object_uri:) distinct :- KnowsRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Person(uri: object_uri);
KnowsRaw(subject_uri: 'http://example.org/hr#alice', object_uri: 'http://example.org/hr#bob');
KnowsRaw(subject_uri: 'http://example.org/hr#bob', object_uri: 'http://example.org/hr#alice');
@OrderBy(ReportsTo, "subject_uri");
ReportsTo(subject_uri:, object_uri:) distinct :- ReportsToRaw(subject_uri:, object_uri:), Employee(uri: subject_uri), Manager(uri: object_uri);
ReportsToRaw(subject_uri: 'http://example.org/hr#alice', object_uri: 'http://example.org/hr#carol');
@OrderBy(WorksAt, "subject_uri");
WorksAt(subject_uri:, object_uri:) distinct :- WorksAtRaw(subject_uri:, object_uri:), Employee(uri: subject_uri), Company(uri: object_uri);
WorksAtRaw(subject_uri: 'http://example.org/hr#alice', object_uri: 'http://example.org/hr#acme');
WorksAtRaw(subject_uri: 'http://example.org/hr#carol', object_uri: 'http://example.org/hr#acme');
Generated SQL and execution results
$ synalog.check('ontology.l')
No errors found.
$ synalog.compile('ontology.l', 'Class')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_0_ClassRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Company' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Employee' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Person' AS uri
) AS UNUSED_TABLE_NAME )
SELECT
ClassRaw.uri AS uri
FROM
t_0_ClassRaw AS ClassRaw
GROUP BY ClassRaw.uri ORDER BY uri;
-- Executed on DuckDB:
| uri |
|--------------------------------|
| http://example.org/hr#Company |
| http://example.org/hr#Employee |
| http://example.org/hr#Manager |
| http://example.org/hr#Person |
(4 rows)
$ synalog.compile('ontology.l', 'SubClassOf')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
CREATE OR REPLACE TABLE logica_home.SubClassOf_ifr0 AS WITH t_2_SubClassOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Employee' AS child_uri,
E'http://example.org/hr#Person' AS parent_uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS child_uri,
E'http://example.org/hr#Employee' AS parent_uri
) AS UNUSED_TABLE_NAME ),
t_4_ClassRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Company' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Employee' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Person' AS uri
) AS UNUSED_TABLE_NAME ),
t_3_Class AS (SELECT
ClassRaw.uri AS uri
FROM
t_4_ClassRaw AS ClassRaw
GROUP BY ClassRaw.uri ORDER BY uri),
t_0_SubClassOf_MultBodyAggAux_f1 AS (SELECT * FROM (
SELECT
SubClassOfRaw.child_uri AS child_uri,
SubClassOfRaw.parent_uri AS parent_uri
FROM
t_2_SubClassOfRaw AS SubClassOfRaw, t_3_Class AS Class, t_3_Class AS t_1_Class
WHERE
(Class.uri = SubClassOfRaw.child_uri) AND
(t_1_Class.uri = SubClassOfRaw.parent_uri)
) AS UNUSED_TABLE_NAME )
SELECT
SubClassOf_MultBodyAggAux_f1.child_uri AS child_uri,
SubClassOf_MultBodyAggAux_f1.parent_uri AS parent_uri
FROM
t_0_SubClassOf_MultBodyAggAux_f1 AS SubClassOf_MultBodyAggAux_f1
GROUP BY SubClassOf_MultBodyAggAux_f1.child_uri, SubClassOf_MultBodyAggAux_f1.parent_uri;
-- Interacting with table logica_home.SubClassOf_ifr0
CREATE OR REPLACE TABLE logica_home.SubClassOf_ifr1 AS WITH t_2_SubClassOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Employee' AS child_uri,
E'http://example.org/hr#Person' AS parent_uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS child_uri,
E'http://example.org/hr#Employee' AS parent_uri
) AS UNUSED_TABLE_NAME ),
t_4_ClassRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Company' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Employee' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Person' AS uri
) AS UNUSED_TABLE_NAME ),
t_3_Class AS (SELECT
ClassRaw.uri AS uri
FROM
t_4_ClassRaw AS ClassRaw
GROUP BY ClassRaw.uri ORDER BY uri),
t_0_SubClassOf_MultBodyAggAux_f2 AS (SELECT * FROM (
SELECT
SubClassOfRaw.child_uri AS child_uri,
SubClassOfRaw.parent_uri AS parent_uri
FROM
t_2_SubClassOfRaw AS SubClassOfRaw, t_3_Class AS Class, t_3_Class AS t_1_Class
WHERE
(Class.uri = SubClassOfRaw.child_uri) AND
(t_1_Class.uri = SubClassOfRaw.parent_uri)
UNION ALL
SELECT
SubClassOf_ifr0.child_uri AS child_uri,
t_2_SubClassOfRaw.parent_uri AS parent_uri
FROM
logica_home.SubClassOf_ifr0 AS SubClassOf_ifr0, t_2_SubClassOfRaw
WHERE
(t_2_SubClassOfRaw.child_uri = SubClassOf_ifr0.parent_uri)
) AS UNUSED_TABLE_NAME )
SELECT
SubClassOf_MultBodyAggAux_f2.child_uri AS child_uri,
SubClassOf_MultBodyAggAux_f2.parent_uri AS parent_uri
FROM
t_0_SubClassOf_MultBodyAggAux_f2 AS SubClassOf_MultBodyAggAux_f2
GROUP BY SubClassOf_MultBodyAggAux_f2.child_uri, SubClassOf_MultBodyAggAux_f2.parent_uri;
-- Interacting with table logica_home.SubClassOf_ifr1
CREATE OR REPLACE TABLE logica_home.SubClassOf_ifr2 AS WITH t_2_SubClassOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Employee' AS child_uri,
E'http://example.org/hr#Person' AS parent_uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS child_uri,
E'http://example.org/hr#Employee' AS parent_uri
) AS UNUSED_TABLE_NAME ),
t_4_ClassRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Company' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Employee' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Person' AS uri
) AS UNUSED_TABLE_NAME ),
t_3_Class AS (SELECT
ClassRaw.uri AS uri
FROM
t_4_ClassRaw AS ClassRaw
GROUP BY ClassRaw.uri ORDER BY uri),
t_0_SubClassOf_MultBodyAggAux_f3 AS (SELECT * FROM (
SELECT
SubClassOfRaw.child_uri AS child_uri,
SubClassOfRaw.parent_uri AS parent_uri
FROM
t_2_SubClassOfRaw AS SubClassOfRaw, t_3_Class AS Class, t_3_Class AS t_1_Class
WHERE
(Class.uri = SubClassOfRaw.child_uri) AND
(t_1_Class.uri = SubClassOfRaw.parent_uri)
UNION ALL
SELECT
SubClassOf_ifr1.child_uri AS child_uri,
t_2_SubClassOfRaw.parent_uri AS parent_uri
FROM
logica_home.SubClassOf_ifr1 AS SubClassOf_ifr1, t_2_SubClassOfRaw
WHERE
(t_2_SubClassOfRaw.child_uri = SubClassOf_ifr1.parent_uri)
) AS UNUSED_TABLE_NAME )
SELECT
SubClassOf_MultBodyAggAux_f3.child_uri AS child_uri,
SubClassOf_MultBodyAggAux_f3.parent_uri AS parent_uri
FROM
t_0_SubClassOf_MultBodyAggAux_f3 AS SubClassOf_MultBodyAggAux_f3
GROUP BY SubClassOf_MultBodyAggAux_f3.child_uri, SubClassOf_MultBodyAggAux_f3.parent_uri;
-- Interacting with table logica_home.SubClassOf_ifr2
CREATE OR REPLACE TABLE logica_home.SubClassOf_ifr1 AS WITH t_2_SubClassOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Employee' AS child_uri,
E'http://example.org/hr#Person' AS parent_uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS child_uri,
E'http://example.org/hr#Employee' AS parent_uri
) AS UNUSED_TABLE_NAME ),
t_4_ClassRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Company' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Employee' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Person' AS uri
) AS UNUSED_TABLE_NAME ),
t_3_Class AS (SELECT
ClassRaw.uri AS uri
FROM
t_4_ClassRaw AS ClassRaw
GROUP BY ClassRaw.uri ORDER BY uri),
t_0_SubClassOf_MultBodyAggAux_f4 AS (SELECT * FROM (
SELECT
SubClassOfRaw.child_uri AS child_uri,
SubClassOfRaw.parent_uri AS parent_uri
FROM
t_2_SubClassOfRaw AS SubClassOfRaw, t_3_Class AS Class, t_3_Class AS t_1_Class
WHERE
(Class.uri = SubClassOfRaw.child_uri) AND
(t_1_Class.uri = SubClassOfRaw.parent_uri)
UNION ALL
SELECT
SubClassOf_ifr2.child_uri AS child_uri,
t_2_SubClassOfRaw.parent_uri AS parent_uri
FROM
logica_home.SubClassOf_ifr2 AS SubClassOf_ifr2, t_2_SubClassOfRaw
WHERE
(t_2_SubClassOfRaw.child_uri = SubClassOf_ifr2.parent_uri)
) AS UNUSED_TABLE_NAME )
SELECT
SubClassOf_MultBodyAggAux_f4.child_uri AS child_uri,
SubClassOf_MultBodyAggAux_f4.parent_uri AS parent_uri
FROM
t_0_SubClassOf_MultBodyAggAux_f4 AS SubClassOf_MultBodyAggAux_f4
GROUP BY SubClassOf_MultBodyAggAux_f4.child_uri, SubClassOf_MultBodyAggAux_f4.parent_uri;
-- Interacting with table logica_home.SubClassOf_ifr1
WITH t_2_SubClassOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Employee' AS child_uri,
E'http://example.org/hr#Person' AS parent_uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS child_uri,
E'http://example.org/hr#Employee' AS parent_uri
) AS UNUSED_TABLE_NAME ),
t_4_ClassRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#Company' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Employee' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Manager' AS uri
UNION ALL
SELECT
E'http://example.org/hr#Person' AS uri
) AS UNUSED_TABLE_NAME ),
t_3_Class AS (SELECT
ClassRaw.uri AS uri
FROM
t_4_ClassRaw AS ClassRaw
GROUP BY ClassRaw.uri ORDER BY uri),
t_0_SubClassOf_MultBodyAggAux_f5 AS (SELECT * FROM (
SELECT
SubClassOfRaw.child_uri AS child_uri,
SubClassOfRaw.parent_uri AS parent_uri
FROM
t_2_SubClassOfRaw AS SubClassOfRaw, t_3_Class AS Class, t_3_Class AS t_1_Class
WHERE
(Class.uri = SubClassOfRaw.child_uri) AND
(t_1_Class.uri = SubClassOfRaw.parent_uri)
UNION ALL
SELECT
SubClassOf_ifr3.child_uri AS child_uri,
t_6_SubClassOfRaw.parent_uri AS parent_uri
FROM
logica_home.SubClassOf_ifr1 AS SubClassOf_ifr3, t_2_SubClassOfRaw AS t_6_SubClassOfRaw
WHERE
(t_6_SubClassOfRaw.child_uri = SubClassOf_ifr3.parent_uri)
) AS UNUSED_TABLE_NAME )
SELECT
SubClassOf_MultBodyAggAux_f5.child_uri AS child_uri,
SubClassOf_MultBodyAggAux_f5.parent_uri AS parent_uri
FROM
t_0_SubClassOf_MultBodyAggAux_f5 AS SubClassOf_MultBodyAggAux_f5
GROUP BY SubClassOf_MultBodyAggAux_f5.child_uri, SubClassOf_MultBodyAggAux_f5.parent_uri;
-- Executed on DuckDB:
| child_uri | parent_uri |
|--------------------------------|--------------------------------|
| http://example.org/hr#Manager | http://example.org/hr#Person |
| http://example.org/hr#Manager | http://example.org/hr#Employee |
| http://example.org/hr#Employee | http://example.org/hr#Person |
(3 rows)
$ synalog.compile('ontology.l', 'Person')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
SELECT
E'http://example.org/hr#bob' AS uri,
null AS email,
E'Bob' AS name
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/hr#bob' || ''), null, (E'Bob' || '') ORDER BY uri;
-- Executed on DuckDB:
| uri | email | name |
|---------------------------|-------|------|
| http://example.org/hr#bob | | Bob |
(1 row)
$ synalog.compile('ontology.l', 'Employee')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
SELECT
E'http://example.org/hr#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice O''Hara' AS name,
90000 AS salary
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/hr#alice' || ''), (E'alice@acme.example' || ''), (E'Alice O''Hara' || ''), 90000 + 0 ORDER BY uri;
-- Executed on DuckDB:
| uri | email | name | salary |
|-----------------------------|--------------------|--------------|--------|
| http://example.org/hr#alice | alice@acme.example | Alice O'Hara | 90000 |
(1 row)
$ synalog.compile('ontology.l', 'Manager')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
SELECT
E'http://example.org/hr#carol' AS uri,
E'carol@acme.example' AS email,
E'Carol' AS name,
150000 AS salary
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/hr#carol' || ''), (E'carol@acme.example' || ''), (E'Carol' || ''), 150000 + 0 ORDER BY uri;
-- Executed on DuckDB:
| uri | email | name | salary |
|-----------------------------|--------------------|-------|--------|
| http://example.org/hr#carol | carol@acme.example | Carol | 150000 |
(1 row)
$ synalog.compile('ontology.l', 'Company')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
SELECT
E'http://example.org/hr#acme' AS uri,
E'Acme "Robotics"' AS name
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/hr#acme' || ''), (E'Acme "Robotics"' || '') ORDER BY uri;
-- Executed on DuckDB:
| uri | name |
|----------------------------|-----------------|
| http://example.org/hr#acme | Acme "Robotics" |
(1 row)
$ synalog.compile('ontology.l', 'WorksAt')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_0_WorksAtRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#alice' AS subject_uri,
E'http://example.org/hr#acme' AS object_uri
UNION ALL
SELECT
E'http://example.org/hr#carol' AS subject_uri,
E'http://example.org/hr#acme' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_1_Employee AS (SELECT
E'http://example.org/hr#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice O''Hara' AS name,
90000 AS salary
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/hr#alice' || ''), (E'alice@acme.example' || ''), (E'Alice O''Hara' || ''), 90000 + 0 ORDER BY uri),
t_2_Company AS (SELECT
E'http://example.org/hr#acme' AS uri,
E'Acme "Robotics"' AS name
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/hr#acme' || ''), (E'Acme "Robotics"' || '') ORDER BY uri)
SELECT
WorksAtRaw.subject_uri AS subject_uri,
WorksAtRaw.object_uri AS object_uri
FROM
t_0_WorksAtRaw AS WorksAtRaw, t_1_Employee AS Employee, t_2_Company AS Company
WHERE
(Employee.uri = WorksAtRaw.subject_uri) AND
(Company.uri = WorksAtRaw.object_uri)
GROUP BY WorksAtRaw.subject_uri, WorksAtRaw.object_uri ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri | object_uri |
|-----------------------------|----------------------------|
| http://example.org/hr#alice | http://example.org/hr#acme |
(1 row)
$ synalog.compile('ontology.l', 'Knows')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_1_KnowsRaw AS (SELECT * FROM (
SELECT
E'http://example.org/hr#alice' AS subject_uri,
E'http://example.org/hr#bob' AS object_uri
UNION ALL
SELECT
E'http://example.org/hr#bob' AS subject_uri,
E'http://example.org/hr#alice' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_2_Person AS (SELECT
E'http://example.org/hr#bob' AS uri,
null AS email,
E'Bob' AS name
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/hr#bob' || ''), null, (E'Bob' || '') ORDER BY uri)
SELECT
KnowsRaw.subject_uri AS subject_uri,
KnowsRaw.object_uri AS object_uri
FROM
t_1_KnowsRaw AS KnowsRaw, t_2_Person AS Person, t_2_Person AS t_0_Person
WHERE
(Person.uri = KnowsRaw.subject_uri) AND
(t_0_Person.uri = KnowsRaw.object_uri)
GROUP BY KnowsRaw.subject_uri, KnowsRaw.object_uri ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri | object_uri |
|-------------|------------|
(0 rows)
$ synalog.compile('ontology.l', 'ReportsTo')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_0_Employee AS (SELECT
E'http://example.org/hr#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice O''Hara' AS name,
90000 AS salary
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/hr#alice' || ''), (E'alice@acme.example' || ''), (E'Alice O''Hara' || ''), 90000 + 0 ORDER BY uri),
t_1_Manager AS (SELECT
E'http://example.org/hr#carol' AS uri,
E'carol@acme.example' AS email,
E'Carol' AS name,
150000 AS salary
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/hr#carol' || ''), (E'carol@acme.example' || ''), (E'Carol' || ''), 150000 + 0 ORDER BY uri)
SELECT
Employee.uri AS subject_uri,
Manager.uri AS object_uri
FROM
t_0_Employee AS Employee, t_1_Manager AS Manager
WHERE
(E'http://example.org/hr#alice' = Employee.uri) AND
(E'http://example.org/hr#carol' = Manager.uri)
GROUP BY Employee.uri, Manager.uri ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri | object_uri |
|-----------------------------|-----------------------------|
| http://example.org/hr#alice | http://example.org/hr#carol |
(1 row)
Every OWL axiom, by example¶
The HR ontology above stays deliberately simple. This second one is the opposite: it packs every OWL axiom and property characteristic import translates into a single file, so you can read the exact Synalog each produces. A few assertions are intentionally inconsistent (flagged inline) so the generated *Violation concepts return rows:
# An ontology that exercises EVERY OWL axiom and property characteristic
# `synalog import` translates. `synalog import axioms.ttl > axioms.l` turns each
# construct below into the matching Synalog rule pattern (closures, derived
# inverses, constraint checks). A few assertions are deliberately inconsistent
# (flagged inline) so the generated *Violation concepts return rows.
@prefix : <http://example.org/co#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
# --- Classes ---------------------------------------------------------------
:Agent a owl:Class .
:Person a owl:Class ; rdfs:subClassOf :Agent . # rdfs:subClassOf
:Organization a owl:Class ; rdfs:subClassOf :Agent .
:Human a owl:Class ; owl:equivalentClass :Person . # owl:equivalentClass
:Desk a owl:Class .
:Employee a owl:Class ; rdfs:subClassOf :Person .
:Contractor a owl:Class ; rdfs:subClassOf :Person .
:Employee owl:disjointWith :Contractor . # owl:disjointWith
# --- Datatype properties (become node columns) -----------------------------
:name a owl:DatatypeProperty ; rdfs:domain :Agent ; rdfs:range xsd:string .
:email a owl:DatatypeProperty ; rdfs:domain :Person ; rdfs:range xsd:string .
# --- Object properties + characteristics -----------------------------------
:ancestorOf a owl:ObjectProperty, owl:TransitiveProperty ; # transitive closure
rdfs:domain :Person ; rdfs:range :Person .
:knows a owl:ObjectProperty, owl:SymmetricProperty ; # reverse unioned in
rdfs:domain :Person ; rdfs:range :Person .
:acquaintedWith a owl:ObjectProperty ; owl:equivalentProperty :knows . # shares knows' facts
:worksAt a owl:ObjectProperty ; rdfs:domain :Person ; rdfs:range :Organization .
:employs a owl:ObjectProperty ; owl:inverseOf :worksAt . # derived opposite direction
:parentOf a owl:ObjectProperty ; rdfs:domain :Person ; rdfs:range :Person .
:fatherOf a owl:ObjectProperty ; rdfs:subPropertyOf :parentOf . # facts flow up to parentOf
:sameTeamAs a owl:ObjectProperty, owl:ReflexiveProperty ; # (x, x) over the domain
rdfs:domain :Person ; rdfs:range :Person .
:hasDesk a owl:ObjectProperty,
owl:FunctionalProperty, owl:InverseFunctionalProperty ; # one desk/person & vice-versa
rdfs:domain :Person ; rdfs:range :Desk .
:supervises a owl:ObjectProperty,
owl:AsymmetricProperty, owl:IrreflexiveProperty ; # both-ways & self-loops are violations
rdfs:domain :Person ; rdfs:range :Person .
# --- Individuals (the ABox) ------------------------------------------------
# A concept is keyed by the type an individual is asserted with (membership is
# data-driven, not inferred through subClassOf), so the people who take part in
# the Person→Person properties are typed :Person directly. :Employee / :Contractor
# carry a single individual each, only to demonstrate the owl:disjointWith check.
:acme a :Organization ; :name "Acme" .
:alice a :Person ; :name "Alice" ; :email "alice@acme.example" ;
:worksAt :acme ; :knows :bob ;
:ancestorOf :bob ; :fatherOf :carol ;
:hasDesk :d1 , :d2 ; # two desks — owl:FunctionalProperty violation
:supervises :bob .
:bob a :Person ; :name "Bob" ;
:ancestorOf :carol ; # alice→bob→carol entails alice ancestorOf carol
:supervises :alice . # alice⇄bob — owl:AsymmetricProperty violation
:carol a :Person ; :name "Carol" ;
:supervises :carol . # self-loop — owl:IrreflexiveProperty violation
:dave a :Employee, :Contractor ; # both disjoint classes — owl:disjointWith violation
:name "Dave" .
:d1 a :Desk . :d2 a :Desk .
:alice owl:sameAs :alice2 . # symmetric + transitive SameAs
:alice owl:differentFrom :bob . # asserted distinct (consistent here)
synalog import axioms.ttl turns it into the program below; the rest of this section walks through it construct by construct.
# run: AncestorOf, Knows, Employs, ParentOf, SameTeamAs, HasDeskFunctionalViolation, SupervisesAsymmetricViolation, SupervisesIrreflexiveViolation, DisjointWithViolation, SameAs
# Generated by `synalog import` from an OWL/RDF ontology.
# 7 classes, 10 object properties, 7 individuals.
# Schema — every class as a concept (the TBox)
@OrderBy(Class, "uri");
Class(uri:) distinct :- ClassRaw(uri:);
ClassRaw(uri: 'http://example.org/co#Agent');
ClassRaw(uri: 'http://example.org/co#Contractor');
ClassRaw(uri: 'http://example.org/co#Desk');
ClassRaw(uri: 'http://example.org/co#Employee');
ClassRaw(uri: 'http://example.org/co#Human');
ClassRaw(uri: 'http://example.org/co#Organization');
ClassRaw(uri: 'http://example.org/co#Person');
# Class hierarchy (transitive subClassOf / equivalentClass), joined through Class
@Recursive(SubClassOf, 100);
SubClassOf(child_uri:, parent_uri:) distinct :- SubClassOfRaw(child_uri:, parent_uri:), Class(uri: child_uri), Class(uri: parent_uri);
SubClassOf(child_uri:, parent_uri:) distinct :- SubClassOf(child_uri:, parent_uri: mid), SubClassOfRaw(child_uri: mid, parent_uri:);
SubClassOfRaw(child_uri: 'http://example.org/co#Contractor', parent_uri: 'http://example.org/co#Person');
SubClassOfRaw(child_uri: 'http://example.org/co#Employee', parent_uri: 'http://example.org/co#Person');
SubClassOfRaw(child_uri: 'http://example.org/co#Human', parent_uri: 'http://example.org/co#Person');
SubClassOfRaw(child_uri: 'http://example.org/co#Organization', parent_uri: 'http://example.org/co#Agent');
SubClassOfRaw(child_uri: 'http://example.org/co#Person', parent_uri: 'http://example.org/co#Agent');
SubClassOfRaw(child_uri: 'http://example.org/co#Person', parent_uri: 'http://example.org/co#Human');
# Concepts — classes that have individuals
@OrderBy(Contractor, "uri");
Contractor(uri:, name:) distinct :- ContractorRaw(uri:, name:);
ContractorRaw(uri: 'http://example.org/co#dave', name: 'Dave');
@OrderBy(Desk, "uri");
Desk(uri:) distinct :- DeskRaw(uri:);
DeskRaw(uri: 'http://example.org/co#d1');
DeskRaw(uri: 'http://example.org/co#d2');
@OrderBy(Employee, "uri");
Employee(uri:, name:) distinct :- EmployeeRaw(uri:, name:);
EmployeeRaw(uri: 'http://example.org/co#dave', name: 'Dave');
@OrderBy(Organization, "uri");
Organization(uri:, name:) distinct :- OrganizationRaw(uri:, name:);
OrganizationRaw(uri: 'http://example.org/co#acme', name: 'Acme');
@OrderBy(Person, "uri");
Person(uri:, email:, name:) distinct :- PersonRaw(uri:, email:, name:);
PersonRaw(uri: 'http://example.org/co#alice', email: 'alice@acme.example', name: 'Alice');
PersonRaw(uri: 'http://example.org/co#bob', email: null, name: 'Bob');
PersonRaw(uri: 'http://example.org/co#carol', email: null, name: 'Carol');
# Concepts — object properties (relationships)
@OrderBy(AcquaintedWith, "subject_uri");
AcquaintedWith(subject_uri:, object_uri:) distinct :- KnowsRaw(subject_uri:, object_uri:);
@OrderBy(AncestorOfStep, "subject_uri");
AncestorOfStep(subject_uri:, object_uri:) distinct :-
AncestorOfRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Person(uri: object_uri);
@OrderBy(AncestorOf, "subject_uri");
@Recursive(AncestorOf, 100);
AncestorOf(subject_uri:, object_uri:) distinct :- AncestorOfStep(subject_uri:, object_uri:);
AncestorOf(subject_uri:, object_uri:) distinct :- AncestorOf(subject_uri:, object_uri: mid), AncestorOfStep(subject_uri: mid, object_uri:);
AncestorOfRaw(subject_uri: 'http://example.org/co#alice', object_uri: 'http://example.org/co#bob');
AncestorOfRaw(subject_uri: 'http://example.org/co#bob', object_uri: 'http://example.org/co#carol');
@OrderBy(Employs, "subject_uri");
Employs(subject_uri:, object_uri:) distinct :- WorksAtRaw(subject_uri: object_uri, object_uri: subject_uri);
@OrderBy(FatherOf, "subject_uri");
FatherOf(subject_uri:, object_uri:) distinct :- FatherOfRaw(subject_uri:, object_uri:);
FatherOfRaw(subject_uri: 'http://example.org/co#alice', object_uri: 'http://example.org/co#carol');
@OrderBy(HasDesk, "subject_uri");
HasDesk(subject_uri:, object_uri:) distinct :- HasDeskRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Desk(uri: object_uri);
HasDeskRaw(subject_uri: 'http://example.org/co#alice', object_uri: 'http://example.org/co#d1');
HasDeskRaw(subject_uri: 'http://example.org/co#alice', object_uri: 'http://example.org/co#d2');
@OrderBy(HasDeskFunctionalViolation, "subject_uri");
HasDeskFunctionalViolation(subject_uri:) distinct :- HasDesk(subject_uri:, object_uri: value_a), HasDesk(subject_uri:, object_uri: value_b), value_a != value_b;
@OrderBy(HasDeskInverseFunctionalViolation, "object_uri");
HasDeskInverseFunctionalViolation(object_uri:) distinct :- HasDesk(subject_uri: subject_a, object_uri:), HasDesk(subject_uri: subject_b, object_uri:), subject_a != subject_b;
@OrderBy(Knows, "subject_uri");
Knows(subject_uri:, object_uri:) distinct :-
KnowsRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Person(uri: object_uri) |
KnowsRaw(subject_uri: object_uri, object_uri: subject_uri), Person(uri: subject_uri), Person(uri: object_uri);
KnowsRaw(subject_uri: 'http://example.org/co#alice', object_uri: 'http://example.org/co#bob');
@OrderBy(ParentOf, "subject_uri");
ParentOf(subject_uri:, object_uri:) distinct :- FatherOfRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Person(uri: object_uri);
@OrderBy(SameTeamAs, "subject_uri");
SameTeamAs(subject_uri:, object_uri:) distinct :- Person(uri: subject_uri), object_uri == subject_uri;
@OrderBy(Supervises, "subject_uri");
Supervises(subject_uri:, object_uri:) distinct :- SupervisesRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Person(uri: object_uri);
SupervisesRaw(subject_uri: 'http://example.org/co#alice', object_uri: 'http://example.org/co#bob');
SupervisesRaw(subject_uri: 'http://example.org/co#bob', object_uri: 'http://example.org/co#alice');
SupervisesRaw(subject_uri: 'http://example.org/co#carol', object_uri: 'http://example.org/co#carol');
@OrderBy(SupervisesAsymmetricViolation, "subject_uri");
SupervisesAsymmetricViolation(subject_uri:, object_uri:) distinct :- Supervises(subject_uri:, object_uri:), Supervises(subject_uri: object_uri, object_uri: subject_uri);
@OrderBy(SupervisesIrreflexiveViolation, "uri");
SupervisesIrreflexiveViolation(uri:) distinct :- Supervises(subject_uri: uri, object_uri: uri);
@OrderBy(WorksAt, "subject_uri");
WorksAt(subject_uri:, object_uri:) distinct :- WorksAtRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Organization(uri: object_uri);
WorksAtRaw(subject_uri: 'http://example.org/co#alice', object_uri: 'http://example.org/co#acme');
# owl:sameAs — symmetric, transitive individual equivalence
@OrderBy(SameAs, "left_uri");
@Recursive(SameAs, 100);
SameAs(left_uri:, right_uri:) distinct :-
SameAsRaw(left_uri:, right_uri:) |
SameAsRaw(left_uri: right_uri, right_uri: left_uri);
SameAs(left_uri:, right_uri:) distinct :- SameAs(left_uri:, right_uri: mid), SameAs(left_uri: mid, right_uri:);
SameAsRaw(left_uri: 'http://example.org/co#alice', right_uri: 'http://example.org/co#alice2');
@OrderBy(DifferentFromViolation, "left_uri");
DifferentFromViolation(left_uri:, right_uri:) distinct :- SameAs(left_uri:, right_uri:), DifferentFromRaw(left_uri:, right_uri:);
DifferentFromRaw(left_uri: 'http://example.org/co#alice', right_uri: 'http://example.org/co#bob');
# owl:disjointWith — an individual typed by two disjoint classes
@OrderBy(DisjointWithViolation, "uri");
DisjointWithViolation(uri:, class_a:, class_b:) distinct :-
Contractor(uri:), Employee(uri:), class_a == 'http://example.org/co#Contractor', class_b == 'http://example.org/co#Employee';
Class axioms¶
rdfs:subClassOf and owl:equivalentClass both feed one recursive SubClassOf — an equivalence is just subclassing both ways (Human ⊑ Person and Person ⊑ Human):
@Recursive(SubClassOf, 100);
SubClassOf(child_uri:, parent_uri:) distinct :- SubClassOfRaw(child_uri:, parent_uri:), Class(uri: child_uri), Class(uri: parent_uri);
SubClassOf(child_uri:, parent_uri:) distinct :- SubClassOf(child_uri:, parent_uri: mid), SubClassOfRaw(child_uri: mid, parent_uri:);
SubClassOfRaw(child_uri: 'http://example.org/co#Person', parent_uri: 'http://example.org/co#Agent');
SubClassOfRaw(child_uri: 'http://example.org/co#Human', parent_uri: 'http://example.org/co#Person');
SubClassOfRaw(child_uri: 'http://example.org/co#Person', parent_uri: 'http://example.org/co#Human');
owl:disjointWith becomes a check — an individual that lands in both classes is a contradiction. :dave is typed as both, so the concept returns him:
@OrderBy(DisjointWithViolation, "uri");
DisjointWithViolation(uri:, class_a:, class_b:) distinct :-
Contractor(uri:), Employee(uri:), class_a == 'http://example.org/co#Contractor', class_b == 'http://example.org/co#Employee';
Property characteristics¶
owl:TransitiveProperty — a one-step …Step predicate plus its @Recursive closure, so alice ancestorOf bob and bob ancestorOf carol entail alice ancestorOf carol:
:ancestorOf a owl:ObjectProperty, owl:TransitiveProperty ; rdfs:domain :Person ; rdfs:range :Person .
AncestorOfStep(subject_uri:, object_uri:) distinct :-
AncestorOfRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Person(uri: object_uri);
@Recursive(AncestorOf, 100);
AncestorOf(subject_uri:, object_uri:) distinct :- AncestorOfStep(subject_uri:, object_uri:);
AncestorOf(subject_uri:, object_uri:) distinct :- AncestorOf(subject_uri:, object_uri: mid), AncestorOfStep(subject_uri: mid, object_uri:);
owl:SymmetricProperty — the reverse direction is unioned in, so the single asserted alice knows bob yields both directions:
Knows(subject_uri:, object_uri:) distinct :-
KnowsRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Person(uri: object_uri) |
KnowsRaw(subject_uri: object_uri, object_uri: subject_uri), Person(uri: subject_uri), Person(uri: object_uri);
owl:ReflexiveProperty — (x, x) for every individual in the domain:
SameTeamAs(subject_uri:, object_uri:) distinct :- Person(uri: subject_uri), object_uri == subject_uri;
owl:FunctionalProperty / owl:InverseFunctionalProperty — a check for a subject (resp. object) that appears with two different values. :alice has two desks, so the functional check flags her:
:hasDesk a owl:ObjectProperty, owl:FunctionalProperty, owl:InverseFunctionalProperty ;
rdfs:domain :Person ; rdfs:range :Desk .
HasDeskFunctionalViolation(subject_uri:) distinct :- HasDesk(subject_uri:, object_uri: value_a), HasDesk(subject_uri:, object_uri: value_b), value_a != value_b;
HasDeskInverseFunctionalViolation(object_uri:) distinct :- HasDesk(subject_uri: subject_a, object_uri:), HasDesk(subject_uri: subject_b, object_uri:), subject_a != subject_b;
owl:AsymmetricProperty / owl:IrreflexiveProperty — a check for a pair that holds both ways (resp. a self-loop). :alice and :bob supervise each other and :carol supervises herself, so both fire:
:supervises a owl:ObjectProperty, owl:AsymmetricProperty, owl:IrreflexiveProperty ;
rdfs:domain :Person ; rdfs:range :Person .
SupervisesAsymmetricViolation(subject_uri:, object_uri:) distinct :- Supervises(subject_uri:, object_uri:), Supervises(subject_uri: object_uri, object_uri: subject_uri);
SupervisesIrreflexiveViolation(uri:) distinct :- Supervises(subject_uri: uri, object_uri: uri);
Relations between properties¶
owl:inverseOf — employs derives its rows by swapping the subject and object of worksAt's facts:
Employs(subject_uri:, object_uri:) distinct :- WorksAtRaw(subject_uri: object_uri, object_uri: subject_uri);
rdfs:subPropertyOf — fatherOf's facts flow up into parentOf:
ParentOf(subject_uri:, object_uri:) distinct :- FatherOfRaw(subject_uri:, object_uri:), Person(uri: subject_uri), Person(uri: object_uri);
owl:equivalentProperty — acquaintedWith includes knows's facts:
Individual axioms¶
owl:sameAs — a symmetric, transitive SameAs concept (the same closure shape as a transitive property):
@Recursive(SameAs, 100);
SameAs(left_uri:, right_uri:) distinct :-
SameAsRaw(left_uri:, right_uri:) |
SameAsRaw(left_uri: right_uri, right_uri: left_uri);
SameAs(left_uri:, right_uri:) distinct :- SameAs(left_uri:, right_uri: mid), SameAs(left_uri: mid, right_uri:);
owl:differentFrom — a check: two individuals asserted different yet inferred SameAs are inconsistent (none are, here):
DifferentFromViolation(left_uri:, right_uri:) distinct :- SameAs(left_uri:, right_uri:), DifferentFromRaw(left_uri:, right_uri:);
Running the headline predicates shows the closures filled in and the *Violation checks firing on the inconsistent rows:
Generated SQL and execution results
$ synalog.check('axioms.l')
No errors found.
$ synalog.compile('axioms.l', 'AncestorOf')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
CREATE OR REPLACE TABLE logica_home.AncestorOf_ifr0 AS WITH t_3_AncestorOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS subject_uri,
E'http://example.org/co#bob' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#bob' AS subject_uri,
E'http://example.org/co#carol' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_5_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_4_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_5_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri),
t_1_AncestorOfStep AS (SELECT
AncestorOfRaw.subject_uri AS subject_uri,
AncestorOfRaw.object_uri AS object_uri
FROM
t_3_AncestorOfRaw AS AncestorOfRaw, t_4_Person AS Person, t_4_Person AS t_2_Person
WHERE
(Person.uri = AncestorOfRaw.subject_uri) AND
(t_2_Person.uri = AncestorOfRaw.object_uri)
GROUP BY AncestorOfRaw.subject_uri, AncestorOfRaw.object_uri ORDER BY subject_uri),
t_0_AncestorOf_MultBodyAggAux_f1 AS (SELECT * FROM (
SELECT
AncestorOfStep.subject_uri AS subject_uri,
AncestorOfStep.object_uri AS object_uri
FROM
t_1_AncestorOfStep AS AncestorOfStep
) AS UNUSED_TABLE_NAME )
SELECT
AncestorOf_MultBodyAggAux_f1.subject_uri AS subject_uri,
AncestorOf_MultBodyAggAux_f1.object_uri AS object_uri
FROM
t_0_AncestorOf_MultBodyAggAux_f1 AS AncestorOf_MultBodyAggAux_f1
GROUP BY AncestorOf_MultBodyAggAux_f1.subject_uri, AncestorOf_MultBodyAggAux_f1.object_uri ORDER BY subject_uri;
-- Interacting with table logica_home.AncestorOf_ifr0
CREATE OR REPLACE TABLE logica_home.AncestorOf_ifr1 AS WITH t_3_AncestorOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS subject_uri,
E'http://example.org/co#bob' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#bob' AS subject_uri,
E'http://example.org/co#carol' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_5_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_4_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_5_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri),
t_1_AncestorOfStep AS (SELECT
AncestorOfRaw.subject_uri AS subject_uri,
AncestorOfRaw.object_uri AS object_uri
FROM
t_3_AncestorOfRaw AS AncestorOfRaw, t_4_Person AS Person, t_4_Person AS t_2_Person
WHERE
(Person.uri = AncestorOfRaw.subject_uri) AND
(t_2_Person.uri = AncestorOfRaw.object_uri)
GROUP BY AncestorOfRaw.subject_uri, AncestorOfRaw.object_uri ORDER BY subject_uri),
t_0_AncestorOf_MultBodyAggAux_f2 AS (SELECT * FROM (
SELECT
AncestorOfStep.subject_uri AS subject_uri,
AncestorOfStep.object_uri AS object_uri
FROM
t_1_AncestorOfStep AS AncestorOfStep
UNION ALL
SELECT
AncestorOf_ifr0.subject_uri AS subject_uri,
t_2_AncestorOfStep.object_uri AS object_uri
FROM
logica_home.AncestorOf_ifr0 AS AncestorOf_ifr0, t_1_AncestorOfStep AS t_2_AncestorOfStep
WHERE
(t_2_AncestorOfStep.subject_uri = AncestorOf_ifr0.object_uri)
) AS UNUSED_TABLE_NAME )
SELECT
AncestorOf_MultBodyAggAux_f2.subject_uri AS subject_uri,
AncestorOf_MultBodyAggAux_f2.object_uri AS object_uri
FROM
t_0_AncestorOf_MultBodyAggAux_f2 AS AncestorOf_MultBodyAggAux_f2
GROUP BY AncestorOf_MultBodyAggAux_f2.subject_uri, AncestorOf_MultBodyAggAux_f2.object_uri ORDER BY subject_uri;
-- Interacting with table logica_home.AncestorOf_ifr1
CREATE OR REPLACE TABLE logica_home.AncestorOf_ifr2 AS WITH t_3_AncestorOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS subject_uri,
E'http://example.org/co#bob' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#bob' AS subject_uri,
E'http://example.org/co#carol' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_5_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_4_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_5_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri),
t_1_AncestorOfStep AS (SELECT
AncestorOfRaw.subject_uri AS subject_uri,
AncestorOfRaw.object_uri AS object_uri
FROM
t_3_AncestorOfRaw AS AncestorOfRaw, t_4_Person AS Person, t_4_Person AS t_2_Person
WHERE
(Person.uri = AncestorOfRaw.subject_uri) AND
(t_2_Person.uri = AncestorOfRaw.object_uri)
GROUP BY AncestorOfRaw.subject_uri, AncestorOfRaw.object_uri ORDER BY subject_uri),
t_0_AncestorOf_MultBodyAggAux_f3 AS (SELECT * FROM (
SELECT
AncestorOfStep.subject_uri AS subject_uri,
AncestorOfStep.object_uri AS object_uri
FROM
t_1_AncestorOfStep AS AncestorOfStep
UNION ALL
SELECT
AncestorOf_ifr1.subject_uri AS subject_uri,
t_2_AncestorOfStep.object_uri AS object_uri
FROM
logica_home.AncestorOf_ifr1 AS AncestorOf_ifr1, t_1_AncestorOfStep AS t_2_AncestorOfStep
WHERE
(t_2_AncestorOfStep.subject_uri = AncestorOf_ifr1.object_uri)
) AS UNUSED_TABLE_NAME )
SELECT
AncestorOf_MultBodyAggAux_f3.subject_uri AS subject_uri,
AncestorOf_MultBodyAggAux_f3.object_uri AS object_uri
FROM
t_0_AncestorOf_MultBodyAggAux_f3 AS AncestorOf_MultBodyAggAux_f3
GROUP BY AncestorOf_MultBodyAggAux_f3.subject_uri, AncestorOf_MultBodyAggAux_f3.object_uri ORDER BY subject_uri;
-- Interacting with table logica_home.AncestorOf_ifr2
CREATE OR REPLACE TABLE logica_home.AncestorOf_ifr1 AS WITH t_3_AncestorOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS subject_uri,
E'http://example.org/co#bob' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#bob' AS subject_uri,
E'http://example.org/co#carol' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_5_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_4_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_5_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri),
t_1_AncestorOfStep AS (SELECT
AncestorOfRaw.subject_uri AS subject_uri,
AncestorOfRaw.object_uri AS object_uri
FROM
t_3_AncestorOfRaw AS AncestorOfRaw, t_4_Person AS Person, t_4_Person AS t_2_Person
WHERE
(Person.uri = AncestorOfRaw.subject_uri) AND
(t_2_Person.uri = AncestorOfRaw.object_uri)
GROUP BY AncestorOfRaw.subject_uri, AncestorOfRaw.object_uri ORDER BY subject_uri),
t_0_AncestorOf_MultBodyAggAux_f4 AS (SELECT * FROM (
SELECT
AncestorOfStep.subject_uri AS subject_uri,
AncestorOfStep.object_uri AS object_uri
FROM
t_1_AncestorOfStep AS AncestorOfStep
UNION ALL
SELECT
AncestorOf_ifr2.subject_uri AS subject_uri,
t_2_AncestorOfStep.object_uri AS object_uri
FROM
logica_home.AncestorOf_ifr2 AS AncestorOf_ifr2, t_1_AncestorOfStep AS t_2_AncestorOfStep
WHERE
(t_2_AncestorOfStep.subject_uri = AncestorOf_ifr2.object_uri)
) AS UNUSED_TABLE_NAME )
SELECT
AncestorOf_MultBodyAggAux_f4.subject_uri AS subject_uri,
AncestorOf_MultBodyAggAux_f4.object_uri AS object_uri
FROM
t_0_AncestorOf_MultBodyAggAux_f4 AS AncestorOf_MultBodyAggAux_f4
GROUP BY AncestorOf_MultBodyAggAux_f4.subject_uri, AncestorOf_MultBodyAggAux_f4.object_uri ORDER BY subject_uri;
-- Interacting with table logica_home.AncestorOf_ifr1
WITH t_3_AncestorOfRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS subject_uri,
E'http://example.org/co#bob' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#bob' AS subject_uri,
E'http://example.org/co#carol' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_5_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_4_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_5_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri),
t_1_AncestorOfStep AS (SELECT
AncestorOfRaw.subject_uri AS subject_uri,
AncestorOfRaw.object_uri AS object_uri
FROM
t_3_AncestorOfRaw AS AncestorOfRaw, t_4_Person AS Person, t_4_Person AS t_2_Person
WHERE
(Person.uri = AncestorOfRaw.subject_uri) AND
(t_2_Person.uri = AncestorOfRaw.object_uri)
GROUP BY AncestorOfRaw.subject_uri, AncestorOfRaw.object_uri ORDER BY subject_uri),
t_0_AncestorOf_MultBodyAggAux_f5 AS (SELECT * FROM (
SELECT
AncestorOfStep.subject_uri AS subject_uri,
AncestorOfStep.object_uri AS object_uri
FROM
t_1_AncestorOfStep AS AncestorOfStep
UNION ALL
SELECT
AncestorOf_ifr3.subject_uri AS subject_uri,
t_7_AncestorOfStep.object_uri AS object_uri
FROM
logica_home.AncestorOf_ifr1 AS AncestorOf_ifr3, t_1_AncestorOfStep AS t_7_AncestorOfStep
WHERE
(t_7_AncestorOfStep.subject_uri = AncestorOf_ifr3.object_uri)
) AS UNUSED_TABLE_NAME )
SELECT
AncestorOf_MultBodyAggAux_f5.subject_uri AS subject_uri,
AncestorOf_MultBodyAggAux_f5.object_uri AS object_uri
FROM
t_0_AncestorOf_MultBodyAggAux_f5 AS AncestorOf_MultBodyAggAux_f5
GROUP BY AncestorOf_MultBodyAggAux_f5.subject_uri, AncestorOf_MultBodyAggAux_f5.object_uri ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri | object_uri |
|-----------------------------|-----------------------------|
| http://example.org/co#alice | http://example.org/co#carol |
| http://example.org/co#alice | http://example.org/co#bob |
| http://example.org/co#bob | http://example.org/co#carol |
(3 rows)
$ synalog.compile('axioms.l', 'Knows')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_3_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_2_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_3_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri),
t_0_Knows_MultBodyAggAux AS (SELECT * FROM (
SELECT
Person.uri AS subject_uri,
t_1_Person.uri AS object_uri
FROM
t_2_Person AS Person, t_2_Person AS t_1_Person
WHERE
(E'http://example.org/co#alice' = Person.uri) AND
(E'http://example.org/co#bob' = t_1_Person.uri)
UNION ALL
SELECT
t_6_Person.uri AS subject_uri,
t_7_Person.uri AS object_uri
FROM
t_2_Person AS t_6_Person, t_2_Person AS t_7_Person
WHERE
(E'http://example.org/co#alice' = t_7_Person.uri) AND
(E'http://example.org/co#bob' = t_6_Person.uri)
) AS UNUSED_TABLE_NAME )
SELECT
Knows_MultBodyAggAux.subject_uri AS subject_uri,
Knows_MultBodyAggAux.object_uri AS object_uri
FROM
t_0_Knows_MultBodyAggAux AS Knows_MultBodyAggAux
GROUP BY Knows_MultBodyAggAux.subject_uri, Knows_MultBodyAggAux.object_uri ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri | object_uri |
|-----------------------------|-----------------------------|
| http://example.org/co#alice | http://example.org/co#bob |
| http://example.org/co#bob | http://example.org/co#alice |
(2 rows)
$ synalog.compile('axioms.l', 'Employs')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
SELECT
E'http://example.org/co#acme' AS subject_uri,
E'http://example.org/co#alice' AS object_uri
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/co#acme' || ''), (E'http://example.org/co#alice' || '') ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri | object_uri |
|----------------------------|-----------------------------|
| http://example.org/co#acme | http://example.org/co#alice |
(1 row)
$ synalog.compile('axioms.l', 'ParentOf')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_2_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_1_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_2_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri)
SELECT
Person.uri AS subject_uri,
t_0_Person.uri AS object_uri
FROM
t_1_Person AS Person, t_1_Person AS t_0_Person
WHERE
(E'http://example.org/co#alice' = Person.uri) AND
(E'http://example.org/co#carol' = t_0_Person.uri)
GROUP BY Person.uri, t_0_Person.uri ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri | object_uri |
|-----------------------------|-----------------------------|
| http://example.org/co#alice | http://example.org/co#carol |
(1 row)
$ synalog.compile('axioms.l', 'SameTeamAs')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_1_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_0_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_1_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri)
SELECT
Person.uri AS subject_uri,
Person.uri AS object_uri
FROM
t_0_Person AS Person
GROUP BY Person.uri, Person.uri ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri | object_uri |
|-----------------------------|-----------------------------|
| http://example.org/co#alice | http://example.org/co#alice |
| http://example.org/co#bob | http://example.org/co#bob |
| http://example.org/co#carol | http://example.org/co#carol |
(3 rows)
$ synalog.compile('axioms.l', 'HasDeskFunctionalViolation')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_2_HasDeskRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS subject_uri,
E'http://example.org/co#d1' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#alice' AS subject_uri,
E'http://example.org/co#d2' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_4_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_3_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_4_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri),
t_6_DeskRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#d1' AS uri
UNION ALL
SELECT
E'http://example.org/co#d2' AS uri
) AS UNUSED_TABLE_NAME ),
t_5_Desk AS (SELECT
DeskRaw.uri AS uri
FROM
t_6_DeskRaw AS DeskRaw
GROUP BY DeskRaw.uri ORDER BY uri),
t_1_HasDesk AS (SELECT
HasDeskRaw.subject_uri AS subject_uri,
HasDeskRaw.object_uri AS object_uri
FROM
t_2_HasDeskRaw AS HasDeskRaw, t_3_Person AS Person, t_5_Desk AS Desk
WHERE
(Person.uri = HasDeskRaw.subject_uri) AND
(Desk.uri = HasDeskRaw.object_uri)
GROUP BY HasDeskRaw.subject_uri, HasDeskRaw.object_uri ORDER BY subject_uri)
SELECT
HasDesk.subject_uri AS subject_uri
FROM
t_1_HasDesk AS HasDesk, t_1_HasDesk AS t_0_HasDesk
WHERE
(HasDesk.object_uri != t_0_HasDesk.object_uri) AND
(t_0_HasDesk.subject_uri = HasDesk.subject_uri)
GROUP BY HasDesk.subject_uri ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri |
|-----------------------------|
| http://example.org/co#alice |
(1 row)
$ synalog.compile('axioms.l', 'SupervisesAsymmetricViolation')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_3_SupervisesRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS subject_uri,
E'http://example.org/co#bob' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#bob' AS subject_uri,
E'http://example.org/co#alice' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#carol' AS subject_uri,
E'http://example.org/co#carol' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_5_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_4_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_5_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri),
t_1_Supervises AS (SELECT
SupervisesRaw.subject_uri AS subject_uri,
SupervisesRaw.object_uri AS object_uri
FROM
t_3_SupervisesRaw AS SupervisesRaw, t_4_Person AS Person, t_4_Person AS t_2_Person
WHERE
(Person.uri = SupervisesRaw.subject_uri) AND
(t_2_Person.uri = SupervisesRaw.object_uri)
GROUP BY SupervisesRaw.subject_uri, SupervisesRaw.object_uri ORDER BY subject_uri)
SELECT
Supervises.subject_uri AS subject_uri,
Supervises.object_uri AS object_uri
FROM
t_1_Supervises AS Supervises, t_1_Supervises AS t_0_Supervises
WHERE
(t_0_Supervises.subject_uri = Supervises.object_uri) AND
(t_0_Supervises.object_uri = Supervises.subject_uri)
GROUP BY Supervises.subject_uri, Supervises.object_uri ORDER BY subject_uri;
-- Executed on DuckDB:
| subject_uri | object_uri |
|-----------------------------|-----------------------------|
| http://example.org/co#alice | http://example.org/co#bob |
| http://example.org/co#bob | http://example.org/co#alice |
| http://example.org/co#carol | http://example.org/co#carol |
(3 rows)
$ synalog.compile('axioms.l', 'SupervisesIrreflexiveViolation')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_2_SupervisesRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS subject_uri,
E'http://example.org/co#bob' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#bob' AS subject_uri,
E'http://example.org/co#alice' AS object_uri
UNION ALL
SELECT
E'http://example.org/co#carol' AS subject_uri,
E'http://example.org/co#carol' AS object_uri
) AS UNUSED_TABLE_NAME ),
t_4_PersonRaw AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS uri,
E'alice@acme.example' AS email,
E'Alice' AS name
UNION ALL
SELECT
E'http://example.org/co#bob' AS uri,
null AS email,
E'Bob' AS name
UNION ALL
SELECT
E'http://example.org/co#carol' AS uri,
null AS email,
E'Carol' AS name
) AS UNUSED_TABLE_NAME ),
t_3_Person AS (SELECT
PersonRaw.uri AS uri,
PersonRaw.email AS email,
PersonRaw.name AS name
FROM
t_4_PersonRaw AS PersonRaw
GROUP BY PersonRaw.uri, PersonRaw.email, PersonRaw.name ORDER BY uri),
t_0_Supervises AS (SELECT
SupervisesRaw.subject_uri AS subject_uri,
SupervisesRaw.object_uri AS object_uri
FROM
t_2_SupervisesRaw AS SupervisesRaw, t_3_Person AS Person, t_3_Person AS t_1_Person
WHERE
(Person.uri = SupervisesRaw.subject_uri) AND
(t_1_Person.uri = SupervisesRaw.object_uri)
GROUP BY SupervisesRaw.subject_uri, SupervisesRaw.object_uri ORDER BY subject_uri)
SELECT
Supervises.subject_uri AS uri
FROM
t_0_Supervises AS Supervises
WHERE
(Supervises.object_uri = Supervises.subject_uri)
GROUP BY Supervises.subject_uri ORDER BY uri;
-- Executed on DuckDB:
| uri |
|-----------------------------|
| http://example.org/co#carol |
(1 row)
$ synalog.compile('axioms.l', 'DisjointWithViolation')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_0_Contractor AS (SELECT
E'http://example.org/co#dave' AS uri,
E'Dave' AS name
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/co#dave' || ''), (E'Dave' || '') ORDER BY uri),
t_1_Employee AS (SELECT
E'http://example.org/co#dave' AS uri,
E'Dave' AS name
FROM
(SELECT 'singleton' as s) as unused_singleton
GROUP BY (E'http://example.org/co#dave' || ''), (E'Dave' || '') ORDER BY uri)
SELECT
Contractor.uri AS uri,
E'http://example.org/co#Contractor' AS class_a,
E'http://example.org/co#Employee' AS class_b
FROM
t_0_Contractor AS Contractor, t_1_Employee AS Employee
WHERE
(Employee.uri = Contractor.uri)
GROUP BY Contractor.uri, (E'http://example.org/co#Contractor' || ''), (E'http://example.org/co#Employee' || '') ORDER BY uri;
-- Executed on DuckDB:
| uri | class_a | class_b |
|----------------------------|----------------------------------|--------------------------------|
| http://example.org/co#dave | http://example.org/co#Contractor | http://example.org/co#Employee |
(1 row)
$ synalog.compile('axioms.l', 'SameAs')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
CREATE OR REPLACE TABLE logica_home.SameAs_ifr0 AS WITH t_0_SameAs_MultBodyAggAux_f6 AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS left_uri,
E'http://example.org/co#alice2' AS right_uri
UNION ALL
SELECT
E'http://example.org/co#alice2' AS left_uri,
E'http://example.org/co#alice' AS right_uri
) AS UNUSED_TABLE_NAME )
SELECT
SameAs_MultBodyAggAux_f6.left_uri AS left_uri,
SameAs_MultBodyAggAux_f6.right_uri AS right_uri
FROM
t_0_SameAs_MultBodyAggAux_f6 AS SameAs_MultBodyAggAux_f6
GROUP BY SameAs_MultBodyAggAux_f6.left_uri, SameAs_MultBodyAggAux_f6.right_uri ORDER BY left_uri;
-- Interacting with table logica_home.SameAs_ifr0
CREATE OR REPLACE TABLE logica_home.SameAs_ifr1 AS WITH t_0_SameAs_MultBodyAggAux_f7 AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS left_uri,
E'http://example.org/co#alice2' AS right_uri
UNION ALL
SELECT
E'http://example.org/co#alice2' AS left_uri,
E'http://example.org/co#alice' AS right_uri
UNION ALL
SELECT
SameAs_ifr0.left_uri AS left_uri,
t_2_SameAs_ifr0.right_uri AS right_uri
FROM
logica_home.SameAs_ifr0 AS SameAs_ifr0, logica_home.SameAs_ifr0 AS t_2_SameAs_ifr0
WHERE
(t_2_SameAs_ifr0.left_uri = SameAs_ifr0.right_uri)
) AS UNUSED_TABLE_NAME )
SELECT
SameAs_MultBodyAggAux_f7.left_uri AS left_uri,
SameAs_MultBodyAggAux_f7.right_uri AS right_uri
FROM
t_0_SameAs_MultBodyAggAux_f7 AS SameAs_MultBodyAggAux_f7
GROUP BY SameAs_MultBodyAggAux_f7.left_uri, SameAs_MultBodyAggAux_f7.right_uri ORDER BY left_uri;
-- Interacting with table logica_home.SameAs_ifr1
CREATE OR REPLACE TABLE logica_home.SameAs_ifr2 AS WITH t_0_SameAs_MultBodyAggAux_f8 AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS left_uri,
E'http://example.org/co#alice2' AS right_uri
UNION ALL
SELECT
E'http://example.org/co#alice2' AS left_uri,
E'http://example.org/co#alice' AS right_uri
UNION ALL
SELECT
SameAs_ifr1.left_uri AS left_uri,
t_2_SameAs_ifr1.right_uri AS right_uri
FROM
logica_home.SameAs_ifr1 AS SameAs_ifr1, logica_home.SameAs_ifr1 AS t_2_SameAs_ifr1
WHERE
(t_2_SameAs_ifr1.left_uri = SameAs_ifr1.right_uri)
) AS UNUSED_TABLE_NAME )
SELECT
SameAs_MultBodyAggAux_f8.left_uri AS left_uri,
SameAs_MultBodyAggAux_f8.right_uri AS right_uri
FROM
t_0_SameAs_MultBodyAggAux_f8 AS SameAs_MultBodyAggAux_f8
GROUP BY SameAs_MultBodyAggAux_f8.left_uri, SameAs_MultBodyAggAux_f8.right_uri ORDER BY left_uri;
-- Interacting with table logica_home.SameAs_ifr2
CREATE OR REPLACE TABLE logica_home.SameAs_ifr1 AS WITH t_0_SameAs_MultBodyAggAux_f9 AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS left_uri,
E'http://example.org/co#alice2' AS right_uri
UNION ALL
SELECT
E'http://example.org/co#alice2' AS left_uri,
E'http://example.org/co#alice' AS right_uri
UNION ALL
SELECT
SameAs_ifr2.left_uri AS left_uri,
t_2_SameAs_ifr2.right_uri AS right_uri
FROM
logica_home.SameAs_ifr2 AS SameAs_ifr2, logica_home.SameAs_ifr2 AS t_2_SameAs_ifr2
WHERE
(t_2_SameAs_ifr2.left_uri = SameAs_ifr2.right_uri)
) AS UNUSED_TABLE_NAME )
SELECT
SameAs_MultBodyAggAux_f9.left_uri AS left_uri,
SameAs_MultBodyAggAux_f9.right_uri AS right_uri
FROM
t_0_SameAs_MultBodyAggAux_f9 AS SameAs_MultBodyAggAux_f9
GROUP BY SameAs_MultBodyAggAux_f9.left_uri, SameAs_MultBodyAggAux_f9.right_uri ORDER BY left_uri;
-- Interacting with table logica_home.SameAs_ifr1
WITH t_0_SameAs_MultBodyAggAux_f10 AS (SELECT * FROM (
SELECT
E'http://example.org/co#alice' AS left_uri,
E'http://example.org/co#alice2' AS right_uri
UNION ALL
SELECT
E'http://example.org/co#alice2' AS left_uri,
E'http://example.org/co#alice' AS right_uri
UNION ALL
SELECT
SameAs_ifr3.left_uri AS left_uri,
t_2_SameAs_ifr3.right_uri AS right_uri
FROM
logica_home.SameAs_ifr1 AS SameAs_ifr3, logica_home.SameAs_ifr1 AS t_2_SameAs_ifr3
WHERE
(t_2_SameAs_ifr3.left_uri = SameAs_ifr3.right_uri)
) AS UNUSED_TABLE_NAME )
SELECT
SameAs_MultBodyAggAux_f10.left_uri AS left_uri,
SameAs_MultBodyAggAux_f10.right_uri AS right_uri
FROM
t_0_SameAs_MultBodyAggAux_f10 AS SameAs_MultBodyAggAux_f10
GROUP BY SameAs_MultBodyAggAux_f10.left_uri, SameAs_MultBodyAggAux_f10.right_uri ORDER BY left_uri;
-- Executed on DuckDB:
| left_uri | right_uri |
|------------------------------|------------------------------|
| http://example.org/co#alice | http://example.org/co#alice |
| http://example.org/co#alice | http://example.org/co#alice2 |
| http://example.org/co#alice2 | http://example.org/co#alice |
| http://example.org/co#alice2 | http://example.org/co#alice2 |
(4 rows)
Large, real-world ontologies¶
The two-layer mapping shines on the big public ontologies, which are almost entirely schema. The Human Disease Ontology (DOID) is a good stress test — point import straight at its URL:
That 28 MB ontology has 14,703 classes, a 17,224-edge subClassOf hierarchy and no individuals — so the output is the schema layer only: one Class (every disease, with its rdfs:label) and the recursive SubClassOf, instead of 14,703 empty per-class predicates. An excerpt:
# Generated by `synalog import` from an OWL/RDF ontology.
# 14703 classes, 2 object properties, 0 individuals.
# Schema — every class as a node (the TBox)
@OrderBy(Class, "uri");
Class(uri:, label:) distinct :- ClassRaw(uri:, label:);
ClassRaw(uri: 'http://purl.obolibrary.org/obo/DOID_0001816', label: 'angiosarcoma');
ClassRaw(uri: 'http://purl.obolibrary.org/obo/DOID_0002116', label: 'pterygium');
ClassRaw(uri: 'http://purl.obolibrary.org/obo/DOID_0014667', label: 'disease of metabolism');
ClassRaw(uri: 'http://purl.obolibrary.org/obo/DOID_0040001', label: 'shrimp allergy');
# … 14,699 more classes …
# Class hierarchy (transitive subClassOf), joined through Class
@Recursive(SubClassOf, 100);
SubClassOf(child_uri:, parent_uri:) distinct :- SubClassOfRaw(child_uri:, parent_uri:), Class(uri: child_uri), Class(uri: parent_uri);
SubClassOf(child_uri:, parent_uri:) distinct :- SubClassOf(child_uri:, parent_uri: mid), SubClassOfRaw(child_uri: mid, parent_uri:);
SubClassOfRaw(child_uri: 'http://purl.obolibrary.org/obo/DOID_0001816', parent_uri: 'http://purl.obolibrary.org/obo/DOID_175');
SubClassOfRaw(child_uri: 'http://purl.obolibrary.org/obo/DOID_0002116', parent_uri: 'http://purl.obolibrary.org/obo/DOID_10124');
SubClassOfRaw(child_uri: 'http://purl.obolibrary.org/obo/DOID_0014667', parent_uri: 'http://purl.obolibrary.org/obo/DOID_4');
# … 17,221 more subClassOf edges …
# Concepts (edges) — DOID has no individuals, so these carry no facts
@OrderBy(IsA, "subject_uri");
IsA(subject_uri:, object_uri:) distinct :- IsARaw(subject_uri:, object_uri:);
The whole thing converts in a few seconds; from there a recursive query over SubClassOf gives you, say, every subtype of a disease by a single ancestor lookup.
Next steps¶
From here it's an ordinary Synalog program: add rules that traverse the edges, compose them, or join them with your other tables — see Knowledge graphs.