diff --git a/Cargo.lock b/Cargo.lock index 838f178..faf216c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2318,7 +2318,7 @@ dependencies = [ [[package]] name = "synalog" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "duckdb", diff --git a/Cargo.toml b/Cargo.toml index 77028e4..934fb23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "synalog" -version = "0.3.0" +version = "0.4.0" edition = "2024" -description = "Logic programming for AI agents — Datalog-family language compiling to optimized SQL" +description = "Logic programming for AI agents: Datalog-family language compiling to optimized SQL" license = "Apache-2.0" repository = "https://github.com/synalinks/synalog" readme = "README.md" diff --git a/docs/examples/axioms.l b/docs/examples/axioms.l new file mode 100644 index 0000000..efdbf62 --- /dev/null +++ b/docs/examples/axioms.l @@ -0,0 +1,124 @@ +# 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'; diff --git a/docs/examples/axioms.log b/docs/examples/axioms.log new file mode 100644 index 0000000..bd2c691 --- /dev/null +++ b/docs/examples/axioms.log @@ -0,0 +1,997 @@ +$ 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) diff --git a/docs/examples/axioms.ttl b/docs/examples/axioms.ttl new file mode 100644 index 0000000..63593ff --- /dev/null +++ b/docs/examples/axioms.ttl @@ -0,0 +1,68 @@ +# 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 : . +@prefix owl: . +@prefix rdfs: . +@prefix xsd: . + +# --- 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) diff --git a/docs/javascripts/playground.js b/docs/javascripts/playground.js index 23cbafc..2f00eab 100644 --- a/docs/javascripts/playground.js +++ b/docs/javascripts/playground.js @@ -150,9 +150,16 @@ verifyWrap.appendChild(document.createTextNode(" Verify")); var runBtn = el("button", "pg-run"); runBtn.textContent = "Compile"; + // "Explain" hands the current program off to an AI chat (Claude / ChatGPT), + // mirroring the docs' Summarize button. Built once boot() has wired up the + // editor so its click handler can read the live source/engine/SQL. + var explainWrap = buildExplainMenu(function () { + return buildExplainPrompt(source(), engine(), sqlView, outPane); + }); toolbar.appendChild(labeled("Engine", engineSel)); toolbar.appendChild(labeled("Predicate", predSel)); toolbar.appendChild(verifyWrap); + toolbar.appendChild(explainWrap); toolbar.appendChild(runBtn); var panes = el("div", "pg-panes"); @@ -364,4 +371,98 @@ c.textContent = text; return c; } + + // AI chat targets for the "Explain" button. Favicons are served via + // DuckDuckGo's icon proxy — hot-linking claude.ai / chatgpt.com directly is + // unreliable behind their Cloudflare bot-mitigation (same rationale as + // javascripts/summarize.js). + var AI_PROVIDERS = [ + { + name: "Claude", + icon: "https://icons.duckduckgo.com/ip3/claude.ai.ico", + buildUrl: function (prompt) { + return "https://claude.ai/new?q=" + encodeURIComponent(prompt); + }, + }, + { + name: "ChatGPT", + icon: "https://icons.duckduckgo.com/ip3/chatgpt.com.ico", + buildUrl: function (prompt) { + return "https://chatgpt.com/?q=" + encodeURIComponent(prompt); + }, + }, + ]; + + // Compose the chat prompt from the live editor state: the Synalog program, + // the selected engine, and the compiled SQL when the last compile succeeded. + function buildExplainPrompt(src, eng, sqlView, outPane) { + var prompt = + "I'm working with Synalog, a Datalog-style logic language that compiles " + + "to SQL. Please explain, step by step and in clear language, what the " + + "following Synalog program does — the concepts and rules it defines and " + + "the result it produces.\n\n" + + "Target SQL engine: " + + (eng || "(default)") + + "\n\nSynalog program:\n```\n" + + src + + "\n```\n"; + var sql = sqlView.state.doc.toString(); + // Skip the SQL block when the pane is showing a compile/verify error or is + // empty, so we never feed an error message to the model as if it were SQL. + if (sql && !outPane.classList.contains("pg-has-error")) { + prompt += "\nWhich Synalog compiles to this SQL:\n```sql\n" + sql + "\n```\n"; + } + return prompt; + } + + // Build the "Explain" pill plus its provider dropdown. getPrompt is called + // lazily on each provider click so it always reflects the current editor. + function buildExplainMenu(getPrompt) { + var wrap = el("div", "pg-explain"); + + var button = el("button", "pg-explain-btn"); + button.type = "button"; + button.setAttribute("aria-label", "Explain with AI"); + button.setAttribute("title", "Explain this program with an AI chat"); + button.innerHTML = + "Explain" + + '"; + + var dropdown = el("div", "pg-explain-dropdown"); + dropdown.style.display = "none"; + + AI_PROVIDERS.forEach(function (provider) { + var option = el("a", "pg-explain-option"); + option.href = "#"; + option.innerHTML = + '' + + "" + + provider.name + + ""; + option.addEventListener("click", function (e) { + e.preventDefault(); + e.stopPropagation(); + window.open(provider.buildUrl(getPrompt()), "_blank", "noopener"); + dropdown.style.display = "none"; + }); + dropdown.appendChild(option); + }); + + button.addEventListener("click", function (e) { + e.stopPropagation(); + dropdown.style.display = + dropdown.style.display === "flex" ? "none" : "flex"; + }); + document.addEventListener("click", function () { + dropdown.style.display = "none"; + }); + + wrap.appendChild(button); + wrap.appendChild(dropdown); + return wrap; + } })(); diff --git a/docs/ontologies.md b/docs/ontologies.md index 3a23a44..0863124 100644 --- a/docs/ontologies.md +++ b/docs/ontologies.md @@ -31,20 +31,7 @@ The output has two layers, so the command works whether an ontology is mostly a | 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: - -| Ontology construct | Synalog output | -| --- | --- | -| `owl:TransitiveProperty` | a recursive `@Recursive` closure of the relationship | -| `owl:SymmetricProperty` | the reverse direction is unioned in | -| `owl:inverseOf` | each property's concept derives the other's inverse | -| `rdfs:subPropertyOf` / `owl:equivalentProperty` | the super-/equivalent property includes the sub-/equivalent one | -| `owl:ReflexiveProperty` | `(x, x)` for every individual in the domain | -| `owl:FunctionalProperty` / `InverseFunctionalProperty` / `AsymmetricProperty` / `IrreflexiveProperty` | a `*Violation` concept that selects the individuals breaking the constraint (a non-empty result means the data is inconsistent) | -| `owl:equivalentClass` | mutual `rdfs:subClassOf` (flows into the `SubClassOf` closure) | -| `owl:disjointWith` | a `DisjointWithViolation` concept (individuals typed by two disjoint classes) | -| `owl:sameAs` | a symmetric + transitive `SameAs` concept | -| `owl:differentFrom` | a `DifferentFromViolation` (individuals asserted different yet inferred same) | +**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](#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. @@ -108,6 +95,163 @@ This small HR ontology (Turtle) has three levels of classes, a few datatype and --8<-- "docs/examples/ontology.log" ``` +## 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: + +```turtle +--8<-- "docs/examples/axioms.ttl" +``` + +`synalog import axioms.ttl` turns it into the program below; the rest of this section walks through it construct by construct. + +```logica +--8<-- "docs/examples/axioms.l" +``` + +### Class axioms + +`rdfs:subClassOf` and `owl:equivalentClass` both feed one recursive `SubClassOf` — an equivalence is just subclassing both ways (`Human` ⊑ `Person` **and** `Person` ⊑ `Human`): + +```turtle +:Person a owl:Class ; rdfs:subClassOf :Agent . +:Human a owl:Class ; owl:equivalentClass :Person . +``` + +```logica +@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: + +```turtle +:Employee owl:disjointWith :Contractor . +:dave a :Employee, :Contractor . +``` + +```logica +@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`: + +```turtle +:ancestorOf a owl:ObjectProperty, owl:TransitiveProperty ; rdfs:domain :Person ; rdfs:range :Person . +``` + +```logica +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: + +```logica +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: + +```logica +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: + +```turtle +:hasDesk a owl:ObjectProperty, owl:FunctionalProperty, owl:InverseFunctionalProperty ; + rdfs:domain :Person ; rdfs:range :Desk . +``` + +```logica +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: + +```turtle +:supervises a owl:ObjectProperty, owl:AsymmetricProperty, owl:IrreflexiveProperty ; + rdfs:domain :Person ; rdfs:range :Person . +``` + +```logica +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: + +```turtle +:employs a owl:ObjectProperty ; owl:inverseOf :worksAt . +``` + +```logica +Employs(subject_uri:, object_uri:) distinct :- WorksAtRaw(subject_uri: object_uri, object_uri: subject_uri); +``` + +**`rdfs:subPropertyOf`** — `fatherOf`'s facts flow up into `parentOf`: + +```turtle +:fatherOf a owl:ObjectProperty ; rdfs:subPropertyOf :parentOf . +``` + +```logica +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: + +```turtle +:acquaintedWith a owl:ObjectProperty ; owl:equivalentProperty :knows . +``` + +```logica +AcquaintedWith(subject_uri:, object_uri:) distinct :- KnowsRaw(subject_uri:, object_uri:); +``` + +### Individual axioms + +**`owl:sameAs`** — a symmetric, transitive `SameAs` concept (the same closure shape as a transitive property): + +```logica +@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): + +```logica +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: + +??? example "Generated SQL and execution results" + + ```text + --8<-- "docs/examples/axioms.log" + ``` + ## Large, real-world ontologies The two-layer mapping shines on the big public ontologies, which are almost entirely schema. The [Human Disease Ontology](https://disease-ontology.org/) (DOID) is a good stress test — point `import` straight at its URL: diff --git a/docs/playground.md b/docs/playground.md index 134d603..36230ad 100644 --- a/docs/playground.md +++ b/docs/playground.md @@ -1,7 +1,7 @@ # Playground -Write Synalog on the left, pick a target engine, and see the compiled SQL on the -right. Everything runs **in your browser** — the parser, compiler and verifier are +Write Synalog up top, pick a target engine, and see the compiled SQL below. +Everything runs **in your browser** — the parser, compiler and verifier are the very same Rust code shipped to WebAssembly, with no server and no data leaving the page. There is no database attached, so the SQL is generated but not executed. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index f8bce78..418a533 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -157,7 +157,6 @@ } .pg-run { - margin-left: auto; font: inherit; font-size: 0.74rem; font-weight: 600; @@ -173,6 +172,75 @@ filter: brightness(1.08); } +/* "Explain with AI" pill + its provider dropdown. The wrapper is the + right-aligned action group: its auto margin pushes Explain + Compile to the + far edge of the toolbar, and it anchors the absolutely-positioned dropdown. */ +.pg-explain { + position: relative; + margin-left: auto; + display: flex; + align-items: center; +} + +.pg-explain-btn { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font: inherit; + font-size: 0.72rem; + font-weight: 600; + padding: 0.3rem 0.7rem; + border: 1px solid var(--md-default-fg-color--lighter); + border-radius: 0.2rem; + background: var(--md-default-bg-color); + color: var(--md-default-fg-color); + cursor: pointer; + transition: background 0.2s, border-color 0.2s; +} + +.pg-explain-btn:hover, +.pg-explain-btn:focus-visible { + background: var(--md-default-fg-color--lightest); + border-color: var(--md-default-fg-color--light); + outline: none; +} + +.pg-explain-btn svg { + flex-shrink: 0; +} + +.pg-explain-dropdown { + position: absolute; + top: calc(100% + 0.35rem); + right: 0; + flex-direction: column; + min-width: 150px; + background: var(--md-default-bg-color); + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 0.4rem; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + z-index: 201; + overflow: hidden; +} + +.pg-explain-option { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.8rem; + color: var(--md-default-fg-color); + text-decoration: none; + font-size: 0.78rem; +} + +.pg-explain-option:hover { + background: var(--md-code-bg-color); +} + +.pg-explain-option img { + border-radius: 3px; +} + /* Editor and SQL output stacked vertically (one above the other). */ .pg-panes { display: flex; diff --git a/pyproject.toml b/pyproject.toml index 2007e1c..be29893 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "synalog" -version = "0.3.0" +version = "0.4.0" description = "Logic programming for AI agents: Datalog-family language compiling to optimized SQL" readme = "README.md" requires-python = ">=3.10" diff --git a/uv.lock b/uv.lock index 42e1407..59d79cb 100644 --- a/uv.lock +++ b/uv.lock @@ -2029,7 +2029,7 @@ wheels = [ [[package]] name = "synalog" -version = "0.3.0" +version = "0.4.0" source = { editable = "." } dependencies = [ { name = "click" },