In this guide we explain how one can migrate from sparqljs to Traqula.
Since the AST generated by our parser has a different format than sparqlJS, Traqula does not form a drop in replacement on AST level. However, Traqula's AST started from SPARQL Algebras AST drifting away only where necessary.
When you want to migrate from SPARQL.JS to Traqula, we suggest comparing the SPARQL.js AST types to Traqula AST types.
In case you do not care about the AST generated, like when you use SparqlJS for query validation, Traqula can be a drop in replacement.
Using SparqlJS you would parse using:
const SparqlParser = require('sparqljs').Parser;
const parser = new SparqlParser();
const parsedQuery = parser.parse(
'PREFIX foaf: <http://xmlns.com/foaf/0.1/> ' +
'SELECT * { ?mickey foaf:name "Mickey Mouse"@en; foaf:knows ?other. }');
Using Traqula you would write:
const SparqlParser = require('@traqula/parser-sparql-1-1').Parser;
const parser = new SparqlParser();
const parsedQuery = parser.parse(
'PREFIX foaf: <http://xmlns.com/foaf/0.1/> ' +
'SELECT * { ?mickey foaf:name "Mickey Mouse"@en; foaf:knows ?other. }');
Using SparqlJS generation is performed like:
const SparqlGenerator = require('sparqljs').Generator;
const generator = new SparqlGenerator({ /* prefixes, baseIRI, factory */ });
const generatedQuery = generator.stringify(parsedQuery);
Using Traqula, it supports round tripping. When no round-tripping is required: :
const SparqlGenerator = require('@traqula/generator-sparql-1-1').Generator;
const generator = new SparqlGenerator();
const generatedQuery = generator.generate(parsedQuery, { /* prefixes, baseIri, factory, origSource */ });
Bellow is a non-exhaustive list of differences between SPARQL.js and Traqula's generated AST. To anyone migrating their code discovering missing information, please consider creating a PR.
The object tracking each VALUES binding has keys prefixed with ? in SPARQL.js but not in Traqula.
VALUES ?var { 'someVal' } would be converted to:
SPARQLJS:
{
"type": "values",
"values": [
{
"?var": {
"termType": "literal",
"value": "someVal"
}
}
]
}
Traqula:
{
"type": "values",
"values": [
{
"var": {
"termType": "literal",
"value": "someVal"
}
}
]
}
SPARQL.js would contain subtypes such as for SELECT nodes like:
{
"type": "query",
"queryType": "CONSTRUCT"
}
In Traqula these kind of subTypes are normalized to all use the key 'subType' and are always lowercased:
{
"type": "query",
"subType": "construct"
}
In SPARQL.js all operators were lowercase, except BNODE.
Traqula applies the small case consistently and thus has lowercase bnode:
SPARQL.js would expand parsed prefixUris by resolving them to the registered PREFIX and BASE.
Traqula does not perform this expansion itself.
Likewise, SPARQL.js would only keep the latest prefix definition for a given prefix and would merge definitions when parsing update queries.
Traqula does not perform iri expansions, leaving this to the algebra transformer.
It als keeps context entries as a list of entries for each query.
Query
PREFIX ex: <https://example.com/>
INSERT DATA { ex:a ex:a ex:a }
PREFIX ex: <https://example.com/other/>
DELETE DATA { ex:a ex:a ex:a }
Becomes in SPARQL.js:
{
"type": "update",
"prefixes": {
"ex": "https://example.com/other/"
},
"updates": [
{
"updateType": "insert",
"insert": [
{
"type": "bgp",
"triples": [
{
"subject": {
"termType": "NamedNode",
"value": "https://example.com/a"
},
"predicate": {
"termType": "NamedNode",
"value": "https://example.com/a"
},
"object": {
"termType": "NamedNode",
"value": "https://example.com/a"
}
}
]
}
]
},
{
"type": "delete",
"delete": [
{
"type": "bgp",
"triple": [
{
"subject": {
"termType": "NamedNode",
"value": "https://example.com/other/a"
},
"predicate": {
"termType": "NamedNode",
"value": "https://example.com/other/a"
},
"object": {
"termType": "NamedNode",
"value": "https://example.com/other/a"
}
}
]
}
]
}
]
}
While Traqula is much more verbose:
{
"type": "update",
"updates": [
{
"context": [
{
"type": "contextDef",
"subType": "prefix",
"key": "ex",
"value": {
"type": "term",
"subType": "namedNode",
"value": "https://example.com/"
}
}
],
"operation": {
"type": "updateOperation",
"subType": "insertdata",
"data": [
{
"type": "pattern",
"subType": "bgp",
"triples": [
{
"type": "triple",
"subject": {
"type": "term",
"subType": "namedNode",
"value": "a",
"prefix": "ex"
},
"predicate": {
"type": "term",
"subType": "namedNode",
"value": "a",
"prefix": "ex"
},
"object": {
"type": "term",
"subType": "namedNode",
"value": "a",
"prefix": "ex"
}
}
]
}
]
}
},
{
"context": [
{
"type": "contextDef",
"subType": "prefix",
"key": "ex",
"value": {
"type": "term",
"subType": "namedNode",
"value": "https://example.com/other/"
}
}
],
"operation": {
"type": "updateOperation",
"subType": "deletedata",
"data": [
{
"type": "pattern",
"subType": "bgp",
"triples": [
{
"type": "triple",
"subject": {
"type": "term",
"subType": "namedNode",
"value": "a",
"prefix": "ex"
},
"predicate": {
"type": "term",
"subType": "namedNode",
"value": "a",
"prefix": "ex"
},
"object": {
"type": "term",
"subType": "namedNode",
"value": "a",
"prefix": "ex"
}
}
]
}
]
}
}
]
}
The reason for changing this is to create an AST that is not too invasive and sticks to providing a tree representation of the grammar. By creating a more consistent AST, Traqula is able to provide an efficient yet generic transformer allowing you to visit or manipulate the AST. The code bellow aggregates the prefix entries into a similar object as that of SPARQL.js:
import { AstTransformer } from "@traqula/rules-sparql-1-1";
const transformer = new AstTransformer();
const context: Record<string, string> = {};
transformer.visitNodeSpecific(ast, {}, {
// Visit nodes of type contextDef
'contextDef': {
// Visit the nodes of subType prefix
'prefix': {
visitor: (prefixDef) => {
context[prefixDef.key] = prefixDef.value.value;
}
}
}
})
In SPARQL.js you could provide a context entry to the generator which could contain prefixes and the generator would generate them as such. In Traqula we do not support such an operation and expect you instead to manipulate the AST directly. Meaning in Traqula you write:
import { Generator } from '@traqula/generator-sparql-1-1';
import { AstTransformer, Astfactory, QuerySelect } from '@traqula/rules-sparql-1-1';
const query: QuerySelect;
const generator = new Generator();
const F = new AstFactory();
query.context.append(
F.contextDefinitionPrefix(
F.gen(),
'ex',
F.termNamed(F.gen(), 'https://example.com/'))
);
The arguments F.gen() create a sourceLocation definition which is a part of our AST for round tripping purposes.
F.gen() simply says to the generator that it should generate this node.