mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
fix: Allow non-variable BGP boedies in SPARQL updates
* fix: SPARQL algebra update * fix: SPARQL algebra bgp only * fix: No SPARQL variables and refactor tests
This commit is contained in:
parent
14736327e7
commit
894d4589d9
8
package-lock.json
generated
8
package-lock.json
generated
@ -7649,7 +7649,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/rdf-isomorphic/-/rdf-isomorphic-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/rdf-isomorphic/-/rdf-isomorphic-1.2.0.tgz",
|
||||||
"integrity": "sha512-Dq+iuWrVuK7q3P4/nychbWhRJ1M5yMAekNJN8f5pjarE8SH9Duy/R0JopVF0I0Q2w0poZlsVKKIBpeG+AdOSAw==",
|
"integrity": "sha512-Dq+iuWrVuK7q3P4/nychbWhRJ1M5yMAekNJN8f5pjarE8SH9Duy/R0JopVF0I0Q2w0poZlsVKKIBpeG+AdOSAw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"rdf-string": "^1.5.0",
|
"rdf-string": "^1.5.0",
|
||||||
"rdf-terms": "^1.6.2"
|
"rdf-terms": "^1.6.2"
|
||||||
@ -8881,13 +8880,14 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"sparqlalgebrajs": {
|
"sparqlalgebrajs": {
|
||||||
"version": "2.4.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-2.5.0.tgz",
|
||||||
"integrity": "sha512-6glKn1uWBsdPuQ4D+4r5m8mgWZoMfiNgip4uyblULTmgISqcbsQzrlrIhWQoZSX95QLLlWlYJufhelQAIRAWKg==",
|
"integrity": "sha512-dNJf4xUj5DFZc/9vQnDU5y9u8l4MfvOkMgx6PAefhTjAK0HHChxLZFF4n6GngWfvEZQ3/HcfmQk3cQo6sT/6bQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"rdf-data-factory": "^1.0.0",
|
"rdf-data-factory": "^1.0.0",
|
||||||
|
"rdf-isomorphic": "^1.2.0",
|
||||||
"rdf-string": "^1.5.0",
|
"rdf-string": "^1.5.0",
|
||||||
"sparqljs": "^3.1.1"
|
"sparqljs": "^3.1.1"
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@
|
|||||||
"rdf-parse": "^1.7.0",
|
"rdf-parse": "^1.7.0",
|
||||||
"rdf-serialize": "^1.1.0",
|
"rdf-serialize": "^1.1.0",
|
||||||
"rdf-terms": "^1.5.1",
|
"rdf-terms": "^1.5.1",
|
||||||
"sparqlalgebrajs": "^2.3.1",
|
"sparqlalgebrajs": "^2.5.0",
|
||||||
"sparqljs": "^3.1.2",
|
"sparqljs": "^3.1.2",
|
||||||
"streamify-array": "^1.0.1",
|
"streamify-array": "^1.0.1",
|
||||||
"uuid": "^8.3.0",
|
"uuid": "^8.3.0",
|
||||||
|
@ -51,6 +51,14 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
|||||||
return op.type === Algebra.types.COMPOSITE_UPDATE;
|
return op.type === Algebra.types.COMPOSITE_UPDATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isBasicGraphPatternWithoutVariables(op: Algebra.Operation): op is Algebra.Bgp {
|
||||||
|
if (op.type !== Algebra.types.BGP) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !(op.patterns as BaseQuad[]).some((pattern): boolean =>
|
||||||
|
someTerms(pattern, (term): boolean => term.termType === 'Variable'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the input operation is of a supported type (DELETE/INSERT or composite of those)
|
* Checks if the input operation is of a supported type (DELETE/INSERT or composite of those)
|
||||||
*/
|
*/
|
||||||
@ -67,7 +75,7 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the input DELETE/INSERT is supported.
|
* Checks if the input DELETE/INSERT is supported.
|
||||||
* This means: no GRAPH statements, no DELETE WHERE.
|
* This means: no GRAPH statements, no DELETE WHERE containing terms of type Variable.
|
||||||
*/
|
*/
|
||||||
private validateDeleteInsert(op: Algebra.DeleteInsert): void {
|
private validateDeleteInsert(op: Algebra.DeleteInsert): void {
|
||||||
const def = defaultGraph();
|
const def = defaultGraph();
|
||||||
@ -81,8 +89,7 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
|||||||
this.logger.warn('GRAPH statement in INSERT clause');
|
this.logger.warn('GRAPH statement in INSERT clause');
|
||||||
throw new NotImplementedHttpError('GRAPH statements are not supported');
|
throw new NotImplementedHttpError('GRAPH statements are not supported');
|
||||||
}
|
}
|
||||||
if (op.where ?? deletes.some((pattern): boolean =>
|
if (!(typeof op.where === 'undefined' || this.isBasicGraphPatternWithoutVariables(op.where))) {
|
||||||
someTerms(pattern, (term): boolean => term.termType === 'Variable'))) {
|
|
||||||
this.logger.warn('WHERE statements are not supported');
|
this.logger.warn('WHERE statements are not supported');
|
||||||
throw new NotImplementedHttpError('WHERE statements are not supported');
|
throw new NotImplementedHttpError('WHERE statements are not supported');
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
|||||||
let handler: SparqlUpdatePatchHandler;
|
let handler: SparqlUpdatePatchHandler;
|
||||||
let source: ResourceStore;
|
let source: ResourceStore;
|
||||||
let startQuads: Quad[];
|
let startQuads: Quad[];
|
||||||
|
const fullfilledDataInsert = 'INSERT DATA { :s1 :p1 :o1 . :s2 :p2 :o2 . }';
|
||||||
|
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
startQuads = [ quad(
|
startQuads = [ quad(
|
||||||
@ -56,6 +57,12 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handle(query: string): Promise<void> {
|
||||||
|
const sparqlPrefix = 'prefix : <http://test.com/>\n';
|
||||||
|
return handler.handle({ identifier: { path: 'path' },
|
||||||
|
patch: { algebra: translate(sparqlPrefix.concat(query), { quads: true }) } as SparqlUpdatePatch });
|
||||||
|
}
|
||||||
|
|
||||||
it('only accepts SPARQL updates.', async(): Promise<void> => {
|
it('only accepts SPARQL updates.', async(): Promise<void> => {
|
||||||
const input = { identifier: { path: 'path' },
|
const input = { identifier: { path: 'path' },
|
||||||
patch: { algebra: {}} as SparqlUpdatePatch };
|
patch: { algebra: {}} as SparqlUpdatePatch };
|
||||||
@ -65,12 +72,7 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles INSERT DATA updates.', async(): Promise<void> => {
|
it('handles INSERT DATA updates.', async(): Promise<void> => {
|
||||||
await handler.handle({ identifier: { path: 'path' },
|
await handle(fullfilledDataInsert);
|
||||||
patch: { algebra: translate(
|
|
||||||
'INSERT DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. ' +
|
|
||||||
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2> }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
expect(await basicChecks(startQuads.concat(
|
expect(await basicChecks(startQuads.concat(
|
||||||
[ quad(namedNode('http://test.com/s1'), namedNode('http://test.com/p1'), namedNode('http://test.com/o1')),
|
[ quad(namedNode('http://test.com/s1'), namedNode('http://test.com/p1'), namedNode('http://test.com/o1')),
|
||||||
quad(namedNode('http://test.com/s2'), namedNode('http://test.com/p2'), namedNode('http://test.com/o2')) ],
|
quad(namedNode('http://test.com/s2'), namedNode('http://test.com/p2'), namedNode('http://test.com/o2')) ],
|
||||||
@ -78,11 +80,7 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles DELETE DATA updates.', async(): Promise<void> => {
|
it('handles DELETE DATA updates.', async(): Promise<void> => {
|
||||||
await handler.handle({ identifier: { path: 'path' },
|
await handle('DELETE DATA { :startS1 :startP1 :startO1 }');
|
||||||
patch: { algebra: translate(
|
|
||||||
'DELETE DATA { <http://test.com/startS1> <http://test.com/startP1> <http://test.com/startO1> }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
expect(await basicChecks(
|
expect(await basicChecks(
|
||||||
[ quad(namedNode('http://test.com/startS2'),
|
[ quad(namedNode('http://test.com/startS2'),
|
||||||
namedNode('http://test.com/startP2'),
|
namedNode('http://test.com/startP2'),
|
||||||
@ -91,11 +89,8 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles DELETE WHERE updates with no variables.', async(): Promise<void> => {
|
it('handles DELETE WHERE updates with no variables.', async(): Promise<void> => {
|
||||||
await handler.handle({ identifier: { path: 'path' },
|
const query = 'DELETE WHERE { :startS1 :startP1 :startO1 }';
|
||||||
patch: { algebra: translate(
|
await handle(query);
|
||||||
'DELETE WHERE { <http://test.com/startS1> <http://test.com/startP1> <http://test.com/startO1> }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
expect(await basicChecks(
|
expect(await basicChecks(
|
||||||
[ quad(namedNode('http://test.com/startS2'),
|
[ quad(namedNode('http://test.com/startS2'),
|
||||||
namedNode('http://test.com/startP2'),
|
namedNode('http://test.com/startP2'),
|
||||||
@ -104,13 +99,8 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles DELETE/INSERT updates with empty WHERE.', async(): Promise<void> => {
|
it('handles DELETE/INSERT updates with empty WHERE.', async(): Promise<void> => {
|
||||||
await handler.handle({ identifier: { path: 'path' },
|
const query = 'DELETE { :startS1 :startP1 :startO1 } INSERT { :s1 :p1 :o1 . } WHERE {}';
|
||||||
patch: { algebra: translate(
|
await handle(query);
|
||||||
'DELETE { <http://test.com/startS1> <http://test.com/startP1> <http://test.com/startO1> }\n' +
|
|
||||||
'INSERT { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. }\n' +
|
|
||||||
'WHERE {}',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
expect(await basicChecks([
|
expect(await basicChecks([
|
||||||
quad(namedNode('http://test.com/startS2'),
|
quad(namedNode('http://test.com/startS2'),
|
||||||
namedNode('http://test.com/startP2'),
|
namedNode('http://test.com/startP2'),
|
||||||
@ -122,14 +112,9 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles composite INSERT/DELETE updates.', async(): Promise<void> => {
|
it('handles composite INSERT/DELETE updates.', async(): Promise<void> => {
|
||||||
await handler.handle({ identifier: { path: 'path' },
|
const query = 'INSERT DATA { :s1 :p1 :o1 . :s2 :p2 :o2 };' +
|
||||||
patch: { algebra: translate(
|
'DELETE WHERE { :s1 :p1 :o1 . :startS1 :startP1 :startO1 }';
|
||||||
'INSERT DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. ' +
|
await handle(query);
|
||||||
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2> };' +
|
|
||||||
'DELETE WHERE { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>.' +
|
|
||||||
'<http://test.com/startS1> <http://test.com/startP1> <http://test.com/startO1> }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
expect(await basicChecks([
|
expect(await basicChecks([
|
||||||
quad(namedNode('http://test.com/startS2'),
|
quad(namedNode('http://test.com/startS2'),
|
||||||
namedNode('http://test.com/startP2'),
|
namedNode('http://test.com/startP2'),
|
||||||
@ -141,14 +126,9 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles composite DELETE/INSERT updates.', async(): Promise<void> => {
|
it('handles composite DELETE/INSERT updates.', async(): Promise<void> => {
|
||||||
await handler.handle({ identifier: { path: 'path' },
|
const query = 'DELETE DATA { :s1 :p1 :o1 . :startS1 :startP1 :startO1 } ;' +
|
||||||
patch: { algebra: translate(
|
'INSERT DATA { :s1 :p1 :o1 . :s2 :p2 :o2 }';
|
||||||
'DELETE DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>.' +
|
await handle(query);
|
||||||
'<http://test.com/startS1> <http://test.com/startP1> <http://test.com/startO1> };' +
|
|
||||||
'INSERT DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. ' +
|
|
||||||
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2> }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
expect(await basicChecks([
|
expect(await basicChecks([
|
||||||
quad(namedNode('http://test.com/startS2'),
|
quad(namedNode('http://test.com/startS2'),
|
||||||
namedNode('http://test.com/startP2'),
|
namedNode('http://test.com/startP2'),
|
||||||
@ -163,80 +143,49 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('rejects GRAPH inserts.', async(): Promise<void> => {
|
it('rejects GRAPH inserts.', async(): Promise<void> => {
|
||||||
const handle = handler.handle({ identifier: { path: 'path' },
|
const query = 'INSERT DATA { GRAPH :graph { :s1 :p1 :o1 } }';
|
||||||
patch: { algebra: translate(
|
await expect(handle(query)).rejects.toThrow(NotImplementedHttpError);
|
||||||
'INSERT DATA { GRAPH <http://test.com/graph> { ' +
|
|
||||||
'<http://test.com/startS1> <http://test.com/startP1> <http://test.com/startO1> } }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
await expect(handle).rejects.toThrow('GRAPH statements are not supported');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects GRAPH deletes.', async(): Promise<void> => {
|
it('rejects GRAPH deletes.', async(): Promise<void> => {
|
||||||
const handle = handler.handle({ identifier: { path: 'path' },
|
const query = 'DELETE DATA { GRAPH :graph { :s1 :p1 :o1 } }';
|
||||||
patch: { algebra: translate(
|
await expect(handle(query)).rejects.toThrow(NotImplementedHttpError);
|
||||||
'DELETE DATA { GRAPH <http://test.com/graph> { ' +
|
|
||||||
'<http://test.com/startS1> <http://test.com/startP1> <http://test.com/startO1> } }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
await expect(handle).rejects.toThrow('GRAPH statements are not supported');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects DELETE/INSERT updates with a non-empty WHERE.', async(): Promise<void> => {
|
it('rejects DELETE/INSERT updates with a non-empty WHERE.', async(): Promise<void> => {
|
||||||
const handle = handler.handle({ identifier: { path: 'path' },
|
const query = 'DELETE { :s1 :p1 :o1 } INSERT { :s1 :p1 :o1 } WHERE { ?s ?p ?o }';
|
||||||
patch: { algebra: translate(
|
await expect(handle(query)).rejects.toThrow(NotImplementedHttpError);
|
||||||
'DELETE { <http://test.com/startS1> <http://test.com/startP1> <http://test.com/startO1> }\n' +
|
});
|
||||||
'INSERT { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. }\n' +
|
|
||||||
'WHERE { ?s ?p ?o }',
|
it('rejects INSERT WHERE updates with a UNION.', async(): Promise<void> => {
|
||||||
{ quads: true },
|
const query = 'INSERT { :s1 :p1 :o1 . } WHERE { { :s1 :p1 :o1 } UNION { :s1 :p1 :o2 } }';
|
||||||
) } as SparqlUpdatePatch });
|
await expect(handle(query)).rejects.toThrow(NotImplementedHttpError);
|
||||||
await expect(handle).rejects.toThrow('WHERE statements are not supported');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects DELETE WHERE updates with variables.', async(): Promise<void> => {
|
it('rejects DELETE WHERE updates with variables.', async(): Promise<void> => {
|
||||||
const handle = handler.handle({ identifier: { path: 'path' },
|
const query = 'DELETE WHERE { ?v :startP1 :startO1 }';
|
||||||
patch: { algebra: translate(
|
await expect(handle(query)).rejects.toThrow(NotImplementedHttpError);
|
||||||
'DELETE WHERE { ?v <http://test.com/startP1> <http://test.com/startO1> }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
await expect(handle).rejects.toThrow('WHERE statements are not supported');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects non-DELETE/INSERT updates.', async(): Promise<void> => {
|
it('rejects non-DELETE/INSERT updates.', async(): Promise<void> => {
|
||||||
const handle = handler.handle({ identifier: { path: 'path' },
|
const query = 'MOVE DEFAULT TO GRAPH :newGraph';
|
||||||
patch: { algebra: translate(
|
await expect(handle(query)).rejects.toThrow(NotImplementedHttpError);
|
||||||
'MOVE DEFAULT TO GRAPH <http://test.com/newGraph>',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
await expect(handle).rejects.toThrow('Only DELETE/INSERT SPARQL update operations are supported');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws the error returned by the store if there is one.', async(): Promise<void> => {
|
it('throws the error returned by the store if there is one.', async(): Promise<void> => {
|
||||||
source.getRepresentation = jest.fn(async(): Promise<any> => {
|
source.getRepresentation = jest.fn(async(): Promise<any> => {
|
||||||
throw new Error('error');
|
throw new Error('error');
|
||||||
});
|
});
|
||||||
|
await expect(handle(fullfilledDataInsert)).rejects.toThrow('error');
|
||||||
const input = { identifier: { path: 'path' },
|
|
||||||
patch: { algebra: translate(
|
|
||||||
'INSERT DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. ' +
|
|
||||||
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2> }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch };
|
|
||||||
await expect(handler.handle(input)).rejects.toThrow('error');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a new resource if it does not exist yet.', async(): Promise<void> => {
|
it('creates a new resource if it does not exist yet.', async(): Promise<void> => {
|
||||||
// There is no initial data
|
|
||||||
startQuads = [];
|
startQuads = [];
|
||||||
source.getRepresentation = jest.fn((): any => {
|
source.getRepresentation = jest.fn((): any => {
|
||||||
throw new NotFoundHttpError();
|
throw new NotFoundHttpError();
|
||||||
});
|
});
|
||||||
|
const query = 'INSERT DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. }';
|
||||||
await handler.handle({ identifier: { path: 'path' },
|
await handle(query);
|
||||||
patch: { algebra: translate(
|
|
||||||
'INSERT DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. }',
|
|
||||||
{ quads: true },
|
|
||||||
) } as SparqlUpdatePatch });
|
|
||||||
expect(await basicChecks(startQuads.concat(
|
expect(await basicChecks(startQuads.concat(
|
||||||
[ quad(namedNode('http://test.com/s1'), namedNode('http://test.com/p1'), namedNode('http://test.com/o1')) ],
|
[ quad(namedNode('http://test.com/s1'), namedNode('http://test.com/p1'), namedNode('http://test.com/o1')) ],
|
||||||
))).toBe(true);
|
))).toBe(true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user