diff --git a/README.md b/README.md index 07e7f73..a9dacf1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Bun CI](https://github.com/julianrojas87/rml-mapper-processor-ts/actions/workflows/build-test.yml/badge.svg)](https://github.com/julianrojas87/rml-mapper-processor-ts/actions/workflows/build-test.yml) [![npm](https://img.shields.io/npm/v/rml-mapper-processor-ts.svg?style=popout)](https://npmjs.com/package/rml-mapper-processor-ts) -Typescript wrappers over the RML-related operations to be reused within the [Connector Architecture](hhttps://the-connector-architecture.github.io/site/docs/1_Home). Currently this repository exposes 2 functions: +Typescript wrappers over the RML-related operations to be reused within the [Connector Architecture](hhttps://the-connector-architecture.github.io/site/docs/1_Home). Currently this repository exposes 3 functions: ### [`js:Y2R`](https://github.com/julianrojas87/rml-mapper-processor-ts/blob/main/processors.ttl#L9) @@ -15,6 +15,9 @@ This processor is executes RML mapping rules using the Java-based [RMLMapper eng Logical sources can be marked as trigger-based (`js:trigger`) to indicate that they will be updated in the future and therefore, triggering new mapping executions. Finally, a path (`js:rmlJar`) to a local RMLMapper can be given. An example definition of the processor is shown next: ```turtle +@prefix : . +@prefix js: . + [ ] a js:RMLMapperReader; js:rmlSource [ js:sourceLocation "dataset/data.xml"; @@ -32,3 +35,179 @@ Logical sources can be marked as trigger-based (`js:trigger`) to indicate that t # js:output ; # This parameter is only needed if no js:rmlTarget are defined js:rmlJar <./rmlmapper-6.3.0-r0-all.jar>. ``` + +### [`js:IncRMLTransformer`](https://github.com/julianrojas87/rml-mapper-processor-ts/blob/main/processors.ttl#L142) + +This processor transforms a given RML mapping document to a Incremental RML (IncRML) version of itself. Concretely, this means that every defined `rr:TriplesMap` (that has at least 1 defined `rr:predicateObjectMap`) is further expanded into 3 new `rr:TriplesMap`s, each one dedicated to handle entity `create`, `update` and `delete` events. This processor can be used within a CA pipeline as follows: + +```turtle +@prefix : . +@prefix js: . +@prefix idlab-fn: . +@prefix as: . +@prefix dc: . +@prefix formats: . + +[ ] a js:IncRMLTransformer; + js:rmlStream ; + js:config [ + js:stateBasePath <./state>; + js:lifeCycleConfig [ + js:predicate ; + js:create [ + js:function idlab-fn:explicitCreate; + js:type as:Create + ]; + js:update [ + js:function idlab-fn:implicitUpdate; + js:type as:Update + ]; + js:delete [ + js:function idlab-fn:implicitDelete; + js:type as:Delete + ] + ]; + js:targetConfig [ # LDES-based target configuration is optional + js:targetPath <./output.nt>; + js:timestampPath dc:modified; + js:versionOfPath dc:isVersionOf; + js:serialization formats:N-Triples; + js:uniqueIRIs true; + js:ldesBaseIRI ; + js:shape + ] + ]; + js:incrmlStream ; + js:bulkMode true. # Optional, to handle multiple mapping files together +``` + +The configuration (`js:config`) of the processor includes a specification of the predicate (`js:lifeCycleConfig/js:predicate`) that will be used to characterize entities and the particular FnO functions that will be used to detect create, update and delete events. + +Optionally, a LDES-based logical target configuration (`js:targetConfig`) can be given to produce unique IRIs for every entity and LDES-specific metadata. + +Taking for example, the above processor configuration and the following RML mapping as input: + +```turtle + a rr:TriplesMap ; + rml:logicalSource [ + a rml:LogicalSource ; + rml:source "data.xml" ; + rml:iterator "//Data" ; + rml:referenceFormulation ql:XPath + ] ; + rr:subjectMap [ + a rr:SubjectMap ; + rr:template "http://ex.org/instance/{prop1/@value}" ; + rr:class "http://ex.org/ns/SomeClass" ; + ] ; + rr:predicateObjectMap [ + rr:predicate "http://ex.org/ns/someProperty" ; + rr:objectMap [ + rml:reference "prop2/@value" + ] + ] . +``` + +The processor will expand the mapping to the following IncRML document: + +```turtle + a rr:TriplesMap ; # Create event + rml:logicalSource _:bn0 ; + rr:subjectMap [ + a rr:FunctionTermMap ; + fnml:functionValue [ + rr:predicateObjectMap [ + rr:predicate fno:executes ; + rr:objectMap [ rr:constant idlab-fn:explicitCreate ] + ], [ + rr:predicate idlab-fn:iri ; + rr:objectMap [ rr:template "http://ex.org/instance/{prop1/@value}" ] + ], [ + rr:predicate idlab-fn:state ; + rr:objectMap [ rr:constant "./state/3cd43073163c2153e4f6b01788350e0d_create_state" ] + ] + ] ; + rml:logicalTarget _:bn1 + ] ; + rr:predicateObjectMap _:bn2 ; + rr:predicateObjectMap [ + rr:predicate ; + rr:objectMap [ rr:constant as:Create ] + ] . + + a rr:TriplesMap ; # Update event + rml:logicalSource _:bn0 ; + rr:subjectMap [ + a rr:FunctionTermMap ; + fnml:functionValue [ + rr:predicateObjectMap [ + rr:predicate fno:executes ; + rr:objectMap [ rr:constant idlab-fn:implicitUpdate ] + ], [ + rr:predicate idlab-fn:iri ; + rr:objectMap [ rr:template "http://ex.org/instance/{prop1/@value}" ] + ], [ + rr:predicate idlab-fn:watchedProperties ; + rr:objectMap [ rr:template "prop0={prop1/@value}" ] + ], [ + rr:predicate idlab-fn:state ; + rr:objectMap [ rr:constant "./state/957ab073163c2153e4f6b01788323ab42_update_state" ] + ] + ] ; + rml:logicalTarget _:bn1 + ] ; + rr:predicateObjectMap _:bn2 ; + rr:predicateObjectMap [ + rr:predicate ; + rr:objectMap [ rr:constant as:Update ] + ] . + + a rr:TriplesMap ; # Delete event + rml:logicalSource _:bn0 ; + rr:subjectMap [ + a rr:FunctionTermMap ; + fnml:functionValue [ + rr:predicateObjectMap [ + rr:predicate fno:executes ; + rr:objectMap [ rr:constant idlab-fn:implicitDelete ] + ], [ + rr:predicate idlab-fn:iri ; + rr:objectMap [ rr:template "http://ex.org/instance/{prop1/@value}" ] + ], [ + rr:predicate idlab-fn:state ; + rr:objectMap [ rr:constant "./state/67af43039445c2153e4f3920a788350fff3_delete_state" ] + ] + ] ; + rml:logicalTarget _:bn1 + ] ; + rr:predicateObjectMap [ + rr:predicate ; + rr:objectMap [ rr:constant as:Delete ] + ] . + +_:bn0 a rml:LogicalSource ; + rml:source "data.xml" ; + rml:iterator "//Data" ; + rml:referenceFormulation ql:XPath . + +_:bn1 a rmlt:EventStreamTarget ; # LDES-based Logical Target + rmlt:target [ + a void:Dataset ; + void:dataDump "./output.nt" + ] ; + rmlt:ldes [ + a ldes:EventStream ; + ldes:timestampPath dc:modified ; + ldes:versionOfPath dc:isVersionOf ; + tree:shape + ] ; + rmlt:serialization formats:N-Triples ; + rmlt:ldesGenereateImmutableIRI true ; + rmlt:ldesBaseIRI . + +_:bn2 a rr:PredicateObjectMap ; + rr:predicate "http://ex.org/ns/someProperty" ; + rr:objectMap [ + rml:reference "prop2/@value" + ] . +``` diff --git a/src/rml/incrml.ts b/src/rml/incrml.ts index bbe2c0a..b421036 100644 --- a/src/rml/incrml.ts +++ b/src/rml/incrml.ts @@ -360,6 +360,7 @@ function generateTriplesMapQuads( literal(ldesTargetConfig.uniqueIRIs.toString(), namedNode(XSD.custom("boolean"))) ), quad(LDES_LT, RMLT.terms.ldes, LDES_TARGET), + quad(LDES_TARGET, RDF.terms.type, LDES.terms.EventStream), quad(FTM, RML.terms.logicalTarget, LDES_LT) ]); // Optional rmlt:ldesBaseIRI @@ -501,14 +502,14 @@ function generateTriplesMapQuads( let c = 0; propExpressions.forEach(prop => { - wpTemplate.push(`prop${c}=${prop}`); + wpTemplate.push(`prop${c}={${prop}}`); c++; }); newTMQuads.push(...[ quad(FV, RR.terms.predicateObjectMap, WATCHED_POM), quad(WATCHED_POM, RR.terms.predicate, IDLAB_FN.terms.watchedProperty), quad(WATCHED_POM, RR.terms.objectMap, WATCHED_OM), - quad(WATCHED_OM, RML.terms.reference, literal(wpTemplate.join("&"))) + quad(WATCHED_OM, RR.terms.template, literal(wpTemplate.join("&"))) ]); } diff --git a/test/incrml.test.ts b/test/incrml.test.ts index 70c171b..14ed281 100644 --- a/test/incrml.test.ts +++ b/test/incrml.test.ts @@ -47,7 +47,7 @@ describe("Functional tests for the rml2incrml Connector Architecture function", a rr:TriplesMap ; rml:logicalSource ; rr:subjectMap [ - a fnml:FunctionTermMap ; + a rr:FunctionTermMap ; fnml:functionValue [ rml:logicalSource ; rr:predicateObjectMap [ @@ -190,8 +190,8 @@ describe("Functional tests for the rml2incrml Connector Architecture function", // Check that the watched properties template is properly defined expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty/@Value}"), null )[0]).toBeDefined(); }); @@ -237,8 +237,8 @@ describe("Functional tests for the rml2incrml Connector Architecture function", // Check that the watched properties template is properly defined expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty/@Value}"), null )[0]).toBeDefined(); }); @@ -304,8 +304,8 @@ describe("Functional tests for the rml2incrml Connector Architecture function", // Check that the watched properties template is properly defined expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty/@Value&prop1=YetAnotherProperty/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty/@Value}&prop1={YetAnotherProperty/@Value}"), null )[0]).toBeDefined(); }); @@ -379,14 +379,14 @@ describe("Functional tests for the rml2incrml Connector Architecture function", // Check that the watched properties templates are properly defined expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty/@Value}"), null )[0]).toBeDefined(); expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=YetAnotherProperty/@Value&prop1=@Name"), + RR.template, + DataFactory.literal("prop0={YetAnotherProperty/@Value}&prop1={@Name}"), null )[0]).toBeDefined(); }); @@ -447,7 +447,7 @@ describe("Functional tests for the rml2incrml Connector Architecture function", expect(new Set(states).size).toBe(states.length); // Check that the watched properties templates are properly defined - expect(store.getQuads(null, RML.reference, DataFactory.literal(""), null).length).toBe(3); + expect(store.getQuads(null, RR.template, DataFactory.literal(""), null).length).toBe(3); } }); @@ -525,14 +525,14 @@ describe("Functional tests for the rml2incrml Connector Architecture function", // Check that the watched properties templates are properly defined expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty/@Value&prop1=SomeProperty/@Name"), + RR.template, + DataFactory.literal("prop0={AnotherProperty/@Value}&prop1={SomeProperty/@Name}"), null )[0]).toBeDefined(); expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty/@Value&prop1=@Name"), + RR.template, + DataFactory.literal("prop0={AnotherProperty/@Value}&prop1={@Name}"), null )[0]).toBeDefined(); }); @@ -596,14 +596,14 @@ describe("Functional tests for the rml2incrml Connector Architecture function", // Check that the watched properties templates are properly defined expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty/@Value&prop1=SomeProperty/@Name"), + RR.template, + DataFactory.literal("prop0={AnotherProperty/@Value}&prop1={SomeProperty/@Name}"), null )[0]).toBeDefined(); expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=YetAnotherProperty/@Value&prop1=@Name"), + RR.template, + DataFactory.literal("prop0={YetAnotherProperty/@Value}&prop1={@Name}"), null )[0]).toBeDefined(); @@ -712,14 +712,14 @@ describe("Functional tests for the rml2incrml Connector Architecture function", // Check that the watched properties templates are properly defined expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty/@Value&prop1=SomeProperty/@Name"), + RR.template, + DataFactory.literal("prop0={AnotherProperty/@Value}&prop1={SomeProperty/@Name}"), null )[0]).toBeDefined(); expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=YetAnotherProperty/@Value&prop1=@Name"), + RR.template, + DataFactory.literal("prop0={YetAnotherProperty/@Value}&prop1={@Name}"), null )[0]).toBeDefined(); @@ -796,14 +796,14 @@ describe("Functional tests for the rml2incrml Connector Architecture function", // Check that the watched properties templates are properly defined expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty1/@Value&prop1=YetAnotherProperty1/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty1/@Value}&prop1={YetAnotherProperty1/@Value}"), null )[0]).toBeDefined(); expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty2/@Value&prop1=YetAnotherProperty2/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty2/@Value}&prop1={YetAnotherProperty2/@Value}"), null )[0]).toBeDefined(); }); @@ -889,26 +889,26 @@ describe("Functional tests for the rml2incrml Connector Architecture function", // Check that the watched properties templates are properly defined expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty1/@Value&prop1=YetAnotherProperty1/@Value&prop2=AnotherProperty4/@Value&prop3=YetAnotherProperty4/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty1/@Value}&prop1={YetAnotherProperty1/@Value}&prop2={AnotherProperty4/@Value}&prop3={YetAnotherProperty4/@Value}"), null )[0]).toBeDefined(); expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty2/@Value&prop1=YetAnotherProperty2/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty2/@Value}&prop1={YetAnotherProperty2/@Value}"), null )[0]).toBeDefined(); expect(store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty2.5/@Value&prop1=YetAnotherProperty2.5/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty2.5/@Value}&prop1={YetAnotherProperty2.5/@Value}"), null )[0]).toBeDefined(); const wp3 = store.getQuads( null, - RML.reference, - DataFactory.literal("prop0=AnotherProperty3/@Value&prop1=YetAnotherProperty3/@Value"), + RR.template, + DataFactory.literal("prop0={AnotherProperty3/@Value}&prop1={YetAnotherProperty3/@Value}"), null ); expect(wp3.length).toBe(2); @@ -1001,6 +1001,4 @@ describe("Functional tests for the rml2incrml Connector Architecture function", ]); await rmlStream.end(); }); - - // TODO test a POM join }); \ No newline at end of file