@rdfc/sparql-ingest-processor-ts
Version:
SPARQL Update function to be within RDF-Connect pipelines
173 lines (172 loc) • 7.3 kB
JavaScript
import { RDF, SHACL } from "@treecg/types";
import { Writer as N3Writer, Parser } from "n3";
import { RdfStore } from "rdf-stores";
import { DataFactory } from "rdf-data-factory";
import { getObjects, getSubjects, splitStore } from "./Utils.js";
const df = new DataFactory();
export const CREATE = (store, forVirtuoso, namedGraph, multipleNamedGraphs) => {
const stores = splitStore(store, forVirtuoso ? 500 : 50000);
return stores.map((subStore, i) => {
return `
INSERT DATA {
${namedGraph ? `GRAPH <${namedGraph}> {` : ""}
${new N3Writer().quadsToString(subStore.getQuads())}
${namedGraph ? `}` : ""}
}
${i === stores.length - 1 ? "" : ";"}
`;
});
};
export const UPDATE = (store, forVirtuoso, namedGraph, multipleNamedGraphs) => {
const formattedQuery = formatQuery(store);
const stores = splitStore(store, forVirtuoso ? 500 : 50000);
const queries = [
`
${namedGraph ? `WITH <${namedGraph}>` : ""}
DELETE {
${formattedQuery[0]}
}
WHERE {
${formattedQuery[0]}
};
`
];
stores.forEach((subStore, i) => {
queries.push(`
INSERT DATA {
${namedGraph ? `GRAPH <${namedGraph}> {` : ""}
${new N3Writer().quadsToString(subStore.getQuads())}
${namedGraph ? `}` : ""}
}
${i === stores.length - 1 ? "" : ";"}
`);
});
return queries;
};
export const DELETE = (store, memberIRIs, memberShapes, namedGraph, multipleNamedGraphs) => {
const deleteBuilder = [];
const whereBuilder = [];
let indexStart = 0;
for (const memberIRI of memberIRIs) {
const formatted = formatQuery(store, memberIRI, memberShapes, indexStart);
deleteBuilder.push(formatted.length > 1 ? formatted[1] : formatted[0]);
whereBuilder.push(formatted[0]);
indexStart++;
}
return `
${namedGraph ? `WITH <${namedGraph}>` : ""}
DELETE {
${deleteBuilder.join("\n")}
} WHERE {
${whereBuilder.join("\n")}
}
`;
};
function formatQuery(memberStore, memberIRI, memberShapes, indexStart = 0) {
const subjectSet = new Set();
const blankNodeMap = new Map();
const queryBuilder = [];
const formattedQueries = [];
let i = indexStart;
if (!memberShapes || memberShapes.length === 0) {
for (const quad of memberStore.getQuads()) {
if (!subjectSet.has(quad.subject.value)) {
subjectSet.add(quad.subject.value);
if (quad.subject.termType === "NamedNode") {
queryBuilder.push(`<${quad.subject.value}> ?p_${i} ?o_${i}.`);
}
else if (quad.subject.termType === "BlankNode") {
if (!blankNodeMap.has(quad.subject.value)) {
blankNodeMap.set(quad.subject.value, `?bn_${i}`);
}
if (quad.object.termType === "BlankNode") {
blankNodeMap.set(quad.object.value, `?bn_ref_${i}`);
}
queryBuilder.push(`${blankNodeMap.get(quad.subject.value)} <${quad.predicate.value}> ${quad.object.termType === "Literal" ? `"${quad.object.value}"^^<${quad.object.datatype.value}>`
: quad.object.termType === "BlankNode" ? `${blankNodeMap.get(quad.object.value)} `
: `<${quad.object.value}>`}.`);
queryBuilder.push(`${blankNodeMap.get(quad.subject.value)} ?p_${i} ?o_${i}.`);
queryBuilder.push(`?s_ref_${i} ?p_ref_${i} ${blankNodeMap.get(quad.subject.value)}.`);
}
i++;
}
}
formattedQueries.push(queryBuilder.join("\n"));
}
else {
const shapeIndex = new Map();
memberShapes.forEach(msh => {
const shapeStore = RdfStore.createDefault();
new Parser().parse(msh).forEach(quad => shapeStore.addQuad(quad));
shapeIndex.set(extractMainTargetClass(shapeStore).value, shapeStore);
});
queryBuilder.push(`<${memberIRI}> ?p_${i} ?o_${i}.`);
const memberType = getObjects(memberStore, df.namedNode(memberIRI), RDF.terms.type)[0];
if (memberType) {
i++;
const mshStore = shapeIndex.get(memberType.value);
const propShapes = getObjects(mshStore, null, SHACL.terms.property, null);
for (const propSh of propShapes) {
const pred = getObjects(mshStore, propSh, SHACL.terms.path, null)[0];
queryBuilder.push(`<${memberIRI}> <${pred.value}> ?subEnt_${i}.`);
queryBuilder.push(`?subEnt_${i} ?p_${i} ?o_${i}.`);
i++;
}
formattedQueries.push(queryBuilder.join("\n"));
}
else {
const deleteQueryBuilder = [];
deleteQueryBuilder.push(`<${memberIRI}> ?p_${i} ?o_${i}.`);
i++;
shapeIndex.forEach(mshStore => {
const propShapes = getObjects(mshStore, null, SHACL.terms.property, null);
queryBuilder.push(" OPTIONAL { ");
for (const propSh of propShapes) {
const pred = getObjects(mshStore, propSh, SHACL.terms.path, null)[0];
queryBuilder.push(`<${memberIRI}> <${pred.value}> ?subEnt_${i}.`);
deleteQueryBuilder.push(`<${memberIRI}> <${pred.value}> ?subEnt_${i}.`);
queryBuilder.push(`?subEnt_${i} ?p_${i} ?o_${i}.`);
deleteQueryBuilder.push(`?subEnt_${i} ?p_${i} ?o_${i}.`);
i++;
}
queryBuilder.push(" }");
});
formattedQueries.push(queryBuilder.join("\n"));
formattedQueries.push(deleteQueryBuilder.join("\n"));
}
}
return formattedQueries;
}
function extractMainTargetClass(store) {
const nodeShapes = getSubjects(store, RDF.terms.type, SHACL.terms.NodeShape, null);
let mainNodeShape = null;
if (nodeShapes && nodeShapes.length > 0) {
for (const ns of nodeShapes) {
const isNotReferenced = getSubjects(store, null, ns, null).length === 0;
if (isNotReferenced) {
if (!mainNodeShape) {
mainNodeShape = ns;
}
else {
throw new Error("There are multiple main node shapes in a given shape."
+ " Unrelated shapes must be given as separate member shapes");
}
}
}
if (mainNodeShape) {
const tcq = getObjects(store, mainNodeShape, SHACL.terms.targetClass, null)[0];
if (tcq) {
return tcq;
}
else {
throw new Error("No target class found in main SHACL Node Shapes");
}
}
else {
throw new Error("No main SHACL Node Shapes found in given member shape");
}
}
else {
throw new Error("No SHACL Node Shapes found in given member shape");
}
}