diff --git a/samples/oxi-test.sparqlbook b/samples/oxi-test.sparqlbook index 495288e..90245f3 100644 --- a/samples/oxi-test.sparqlbook +++ b/samples/oxi-test.sparqlbook @@ -22,5 +22,23 @@ "language": "sparql", "value": "# [endpoint=./rdf/a.ttl]\n\n\nDESCRIBE \n", "metadata": {} + }, + { + "kind": 2, + "language": "sparql", + "value": "# [endpoint=./rdf/a.trig]\n# [use_default_graph_as_union=true]\n\n\nSELECT * WHERE {\n ?s ?p ?o\n}", + "metadata": {} + }, + { + "kind": 2, + "language": "sparql", + "value": "# [endpoint=./rdf/a.trig]\n\n\nSELECT * WHERE {\n ?s ?p ?o\n}", + "metadata": {} + }, + { + "kind": 2, + "language": "sparql", + "value": "# [endpoint=./rdf/a.trig]\n# [use_default_graph_as_union=false]\n\n\nSELECT * WHERE {\n ?s ?p ?o\n}", + "metadata": {} } ] \ No newline at end of file diff --git a/samples/rdf/a.trig b/samples/rdf/a.trig new file mode 100644 index 0000000..4699a57 --- /dev/null +++ b/samples/rdf/a.trig @@ -0,0 +1,14 @@ +@prefix ex: . + +# Default graph +ex:subjectDG ex:predicate1 ex:object1 . + +# Named graph 1 +GRAPH { + ex:subjectG1 ex:predicate2 ex:object2 . +} + +# Named graph 2 +GRAPH { + ex:subjectG2 ex:predicate3 ex:object3 . +} \ No newline at end of file diff --git a/src/extension/endpoint/file-endpoint/file-endpoint.ts b/src/extension/endpoint/file-endpoint/file-endpoint.ts index 66762c9..4097a93 100644 --- a/src/extension/endpoint/file-endpoint/file-endpoint.ts +++ b/src/extension/endpoint/file-endpoint/file-endpoint.ts @@ -35,6 +35,10 @@ export class FileEndpoint extends Endpoint { return this.#url; } + useDefaultGraphAsUnion() { + this.#store.setQueryOptions({ use_default_graph_as_union: true }); + } + /** * Adds a file to the endpoint. * diff --git a/src/extension/endpoint/model/sparql-query.ts b/src/extension/endpoint/model/sparql-query.ts index e27922a..2107a49 100644 --- a/src/extension/endpoint/model/sparql-query.ts +++ b/src/extension/endpoint/model/sparql-query.ts @@ -2,7 +2,6 @@ import { window } from "vscode"; import { SPARQLQueryKind } from "../enum/sparql-query-kind"; import { getSPARQLQueryKind } from "../sparql-utils"; -import * as glob from 'glob'; export class SparqlQuery { readonly #queryString: string; @@ -64,6 +63,26 @@ export class SparqlQuery { return this.#extractedEndpointCollection; } + extractQueryOptions(): Map { + const commentLines = this.#queryString + .split("\n") + .map((l) => l.trim()) + .filter((l) => l.startsWith("#")); + const queryOptionsMap = new Map(); + commentLines.forEach((comment: string) => { + const optionExp = /\[([a-zA-Z_]+)=([^\]]+)\]/gm; + const match = optionExp.exec(comment); + + if (match) { + if (match[1] !== 'endpoint') { + queryOptionsMap.set(match[1], match[2]); + } + } + + }); + return queryOptionsMap; + + } } diff --git a/src/extension/notebook/sparql-notebook-controller.ts b/src/extension/notebook/sparql-notebook-controller.ts index a649057..7ca9ad3 100644 --- a/src/extension/notebook/sparql-notebook-controller.ts +++ b/src/extension/notebook/sparql-notebook-controller.ts @@ -8,6 +8,7 @@ import { shrink } from '@zazuko/prefixes'; import { EndpointKind, SparqlQuery } from '../endpoint/model/sparql-query'; import { MimeType } from '../enum/mime-type'; import path = require('path'); +import { Option } from '@vscode/webview-ui-toolkit'; export class SparqlNotebookController { readonly controllerId = `${extensionId}-controller-id`; readonly notebookType = extensionId; @@ -241,6 +242,8 @@ export class SparqlNotebookController { */ private async _getDocumentOrConnectionClient(cell: SparqlNotebookCell, sparqlQuery: SparqlQuery): Promise { const documentEndpoints = sparqlQuery.extractEndpoint().getEndpoints(); + const queryOptions = sparqlQuery.extractQueryOptions(); + if (documentEndpoints.length > 0) { @@ -254,6 +257,14 @@ export class SparqlNotebookController { for (const extractFileEndpoint of documentEndpoints) { await this.#loadFileToStore(extractFileEndpoint.endpoint, fileEndpoint); } + if (queryOptions.size > 0) { + const hasUseDefaultGraphAsUnion = queryOptions.get('use_default_graph_as_union'); + if (hasUseDefaultGraphAsUnion && hasUseDefaultGraphAsUnion === 'true') { + fileEndpoint.useDefaultGraphAsUnion(); + + } + + } return fileEndpoint; } } diff --git a/src/extension/sparql-store/sparql-store.ts b/src/extension/sparql-store/sparql-store.ts index cd36c6e..bf2a56f 100644 --- a/src/extension/sparql-store/sparql-store.ts +++ b/src/extension/sparql-store/sparql-store.ts @@ -1,4 +1,4 @@ -import { Quad, Store, defaultGraph } from 'oxigraph'; +import { BlankNode, NamedNode, Quad, Store, defaultGraph } from 'oxigraph'; import { SPARQLQueryKind } from '../endpoint/enum/sparql-query-kind'; import { SparqlQuery } from '../endpoint/model/sparql-query'; @@ -10,6 +10,17 @@ export enum RdfMimeType { nQuads = 'application/n-quads', } +const allowedQueryOptions = ['use_default_graph_as_union']; + + +interface QueryOptions { + base_iri: string, // base IRI to resolve relative IRIs in the query + use_default_graph_as_union: boolean, // the default graph in the query is the union of all the dataset graphs + default_graph: (NamedNode | BlankNode)[], // the default graph of the query is the union of the store default graph and the http://example.com graph + named_graphs: (NamedNode | BlankNode)[], // we restrict the available named graphs to the two listed + results_format: string, // the response will be serialized a string in the JSON format (media types like application/sparql-results+json also work) +} + /** * This is the local SPARQL endpoint that is used to execute SPARQL queries on the local RDF store. * It is based on the Oxigraph. @@ -17,11 +28,17 @@ export enum RdfMimeType { export class SparqlStore { // the oxigraph store readonly #store: Store; + #queryOptions: Partial = {}; + constructor() { this.#store = new Store(); } + setQueryOptions(options: Partial) { + this.#queryOptions = { ...this.setQueryOptions, ...options }; + } + /** * Execute a SPARQL CONSTRUCT query and return the result as a turtle string. * @@ -33,10 +50,10 @@ export class SparqlStore { if (query.kind !== SPARQLQueryKind.construct) { throw new Error('Query is not a CONSTRUCT query'); } + const options = this.#queryOptions; + options.results_format = RdfMimeType.turtle; - const queryResult = this.#store.query(query.queryString, { - results_format: RdfMimeType.turtle, - }) as string; + const queryResult = this.#store.query(query.queryString, options) as string; return queryResult; } @@ -53,7 +70,8 @@ export class SparqlStore { throw new Error('Query is not a CONSTRUCT query'); } - const queryResult = this.#store.query(query.queryString) as Quad[]; + const options = this.#queryOptions; + const queryResult = this.#store.query(query.queryString, options) as Quad[]; return queryResult; } @@ -70,9 +88,9 @@ export class SparqlStore { throw new Error('Query is not a SELECT query'); } - const queryResult = this.#store.query(query.queryString, { - results_format: 'json', - }) as string; + const options = this.#queryOptions; + options.results_format = 'json'; + const queryResult = this.#store.query(query.queryString, options) as string; return queryResult; } @@ -87,9 +105,9 @@ export class SparqlStore { if (query.kind !== SPARQLQueryKind.ask) { throw new Error('Query is not an ASK query'); } - const result = this.#store.query(query.queryString, { - results_format: "json", - }) as string; + const options = this.#queryOptions; + options.results_format = "json"; + const result = this.#store.query(query.queryString, options) as string; return result; } @@ -104,9 +122,9 @@ export class SparqlStore { throw new Error('Query is not a DESCRIBE query'); } - const queryResult = this.#store.query(query.queryString, { - results_format: RdfMimeType.turtle, - }) as string; + const options = this.#queryOptions; + options.results_format = RdfMimeType.turtle; + const queryResult = this.#store.query(query.queryString, options) as string; return queryResult; } @@ -115,8 +133,8 @@ export class SparqlStore { if (query.kind !== SPARQLQueryKind.describe) { throw new Error('Query is not a DESCRIBE query'); } - - const queryResult = this.#store.query(query.queryString) as Quad[]; + const options = this.#queryOptions; + const queryResult = this.#store.query(query.queryString, options) as Quad[]; return queryResult; } @@ -163,9 +181,11 @@ export class SparqlStore { } -/** - const fakeHttpResult = { - headers: { "content-type": "application/sparql-results+json" }, - data: JSON.parse(res) - }; - */ \ No newline at end of file + +interface QueryOptions { + base_iri: string, // base IRI to resolve relative IRIs in the query + use_default_graph_as_union: boolean, // the default graph in the query is the union of all the dataset graphs + default_graph: (NamedNode | BlankNode)[], // the default graph of the query is the union of the store default graph and the http://example.com graph + named_graphs: (NamedNode | BlankNode)[], // we restrict the available named graphs to the two listed + results_format: string, // the response will be serialized a string in the JSON format (media types like application/sparql-results+json also work) +} \ No newline at end of file