Skip to content

Commit

Permalink
Merge pull request #83 from zazuko:oxi-select-json-result
Browse files Browse the repository at this point in the history
Oxi-select-json-result
  • Loading branch information
BenjaminHofstetter authored Aug 2, 2024
2 parents 2d263d7 + 09587ee commit 34b40e0
Show file tree
Hide file tree
Showing 7 changed files with 742 additions and 373 deletions.
4 changes: 2 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/naming-convention": "warn",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/semi": "warn",
"curly": "warn",
"eqeqeq": "warn",
Expand All @@ -21,4 +21,4 @@
"dist",
"**/*.d.ts"
]
}
}
865 changes: 622 additions & 243 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@
"@vscode/webview-ui-toolkit": "^1.4.0",
"@zazuko/prefixes": "^2.2.0",
"axios": "^1.6.7",
"oxigraph": "^0.3.22",
"oxigraph": "0.4.0-alpha.7",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
32 changes: 32 additions & 0 deletions samples/oxi-test.sparqlbook
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"kind": 2,
"language": "sparql",
"value": "# [endpoint=./rdf/curia.ttl]\n\n\nSELECT ?opti ?s ?p ?o\nWHERE {\n ?s ?p ?o .\n optional {\n ?s <https://foo.ch/bar/> ?opti .\n }\n} LIMIT 10",
"metadata": {}
},
{
"kind": 2,
"language": "sparql",
"value": "# [endpoint=./rdf/curia.ttl]\n\nASK WHERE {\n \n ?s <https://foo.ch/bar/> ?opti .\n \n}",
"metadata": {}
},
{
"kind": 2,
"language": "sparql",
"value": "# [endpoint=./rdf/curia.ttl]\n\n\nCONSTRUCT {\n ?s ?p ?o .\n}\nWHERE {\n ?s ?p ?o .\n \n} LIMIT 10",
"metadata": {}
},
{
"kind": 2,
"language": "sparql",
"value": "# [endpoint=./rdf/curia.ttl]\n\n\nDESCRIBE <https://politics.ld.admin.ch/person/1122>",
"metadata": {}
},
{
"kind": 1,
"language": "markdown",
"value": "",
"metadata": {}
}
]
16 changes: 8 additions & 8 deletions src/extension/endpoint/file-endpoint/file-endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { SparqlQuery } from '../model/sparql-query';
* Represents an HTTP SPARQL endpoint.
*/
export class FileEndpoint extends Endpoint {
private _url: string = '';
private files: Set<Uri> = new Set<Uri>();
private _store: SparqlStore;
#url: string = '';
readonly #files: Set<Uri> = new Set<Uri>();
readonly #store: SparqlStore;

/**
* Creates a new instance of the HttpEndpoint class.
Expand All @@ -22,7 +22,7 @@ export class FileEndpoint extends Endpoint {
*/
constructor() {
super();
this._store = new SparqlStore();
this.#store = new SparqlStore();
}

/**
Expand All @@ -31,7 +31,7 @@ export class FileEndpoint extends Endpoint {
* @returns The URL of the SPARQL endpoint.
*/
get url(): string {
return this._url;
return this.#url;
}

/**
Expand All @@ -40,7 +40,7 @@ export class FileEndpoint extends Endpoint {
* @param rdfFile - The file to add.
*/
public async addFile(rdfFile: Uri): Promise<void> {
this._url = rdfFile.path;
this.#url = rdfFile.path;
if (!rdfFile) {
// show window error message
window.showErrorMessage('No file selected');
Expand All @@ -66,7 +66,7 @@ export class FileEndpoint extends Endpoint {

try {
const fileContent = await fs.promises.readFile(rdfFile.fsPath, 'utf-8');
this._store.load(fileContent, mimeType);
this.#store.load(fileContent, mimeType);
} catch (e: any) {
const message = e.message ?? e;
window.showErrorMessage(`File error: ${message}`);
Expand All @@ -80,7 +80,7 @@ export class FileEndpoint extends Endpoint {
* @param execution - The execution object.
*/
public async query(sparqlQuery: SparqlQuery, execution?: any): Promise<any> {
const res = this._store.query(sparqlQuery);
const res = this.#store.query(sparqlQuery);
return res;
}
}
31 changes: 16 additions & 15 deletions src/extension/notebook/sparql-notebook-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,23 +78,24 @@ export class SparqlNotebookController {
}

// execute the query
const queryResult = await sparqlEndpoint.query(sparqlQuery, execution).catch((error) => {
let errorMessage = error.message ?? "error";
if (error.hasOwnProperty("response") && error.response.hasOwnProperty("data")) {
if (error.response.data.message) {
errorMessage += "\n" + error.response.data.message;
} else {
errorMessage += "\n" + error.response.data;
const queryResult = await sparqlEndpoint.query(sparqlQuery, execution).catch(
(error) => {
let errorMessage = error.message ?? "error";
if (error.hasOwnProperty("response") && error.response.hasOwnProperty("data")) {
if (error.response.data.message) {
errorMessage += "\n" + error.response.data.message;
} else {
errorMessage += "\n" + error.response.data;
}
}
}

execution.replaceOutput([
this._writeError(errorMessage)
]);
console.error('SPARQL execution error:', errorMessage);
execution.end(false, Date.now());
return;
});
execution.replaceOutput([
this._writeError(errorMessage)
]);
console.error('SPARQL execution error:', errorMessage);
execution.end(false, Date.now());
return;
});

// content type
if (!queryResult) {
Expand Down
165 changes: 61 additions & 104 deletions src/extension/sparql-store/sparql-store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Store, defaultGraph } from 'oxigraph';
import { getSPARQLQueryKind } from '../endpoint/sparql-utils';
import { SparqlResultJson } from '../endpoint/model/sparql-result-json';
import { SPARQLQueryKind } from '../endpoint/enum/sparql-query-kind';
import { SparqlQuery } from '../endpoint/model/sparql-query';

Expand All @@ -12,44 +10,46 @@ export enum RdfMimeType {
nQuads = 'application/n-quads',
}
export class SparqlStore {
private store: Store;
// the oxigraph store
readonly #store: Store;

constructor() {
this.store = new Store();
this.#store = new Store();
}

/**
* Can be a SELECT, ASK, CONSTRUCT or DESCRIBE query
*
* @param query SPARQL query
* @returns
*/
public async query(query: SparqlQuery): Promise<any> {
// Executes a SPARQL 1.1 Query.
// For SELECT queries the return type is an array of Map which keys are the bound variables and values are the values the result is bound to.
// For CONSTRUCT and DESCRIBE queries the return type is an array of Quad.
// For ASK queries the return type is a boolean.

const queryKind = query.kind;

if (queryKind === SPARQLQueryKind.ask) {
const res = await this._ask(query);
const fakeHttpResult = {
// eslint-disable-next-line @typescript-eslint/naming-convention
headers: { "content-type": "application/sparql-results+json" },
data: res
};
return fakeHttpResult;
} else if (queryKind === SPARQLQueryKind.select) {
const res = await this._select(query);
const fakeHttpResult = {
// eslint-disable-next-line @typescript-eslint/naming-convention
headers: { "content-type": "application/sparql-results+json" },
data: res
};
return fakeHttpResult;
} else if (queryKind === SPARQLQueryKind.construct || queryKind === SPARQLQueryKind.describe) {
return await this._construct(query);
switch (queryKind) {
case SPARQLQueryKind.ask:
case SPARQLQueryKind.select:
const res = await this._select(query);
const fakeHttpResult = {
headers: { "content-type": "application/sparql-results+json" },
data: JSON.parse(res)
};
return fakeHttpResult;
case SPARQLQueryKind.construct:
case SPARQLQueryKind.describe:
return this._construct(query);
default:
return await this.#store.query(query.queryString, {});
}
return await this.store.query(query.queryString);

}

private async _construct(query: SparqlQuery): Promise<any> {
const ttl = (new Store(this.store.query(query.queryString))).dump(RdfMimeType.turtle, defaultGraph());
const ttl = (new Store(this.#store.query(query.queryString, {}))).dump(RdfMimeType.turtle, defaultGraph());
const fakeHttpResult = {
// eslint-disable-next-line @typescript-eslint/naming-convention
headers: { "content-type": RdfMimeType.turtle },
Expand All @@ -58,96 +58,53 @@ export class SparqlStore {
return fakeHttpResult;
}

private async _ask(query: SparqlQuery): Promise<SparqlResultJson> {
const result = await this.store.query(query.queryString) as boolean;
// turn this boolean to sparql-result+json
return {
"head": {
"vars": []
},
"boolean": result
};
}

private async _select(query: SparqlQuery): Promise<SparqlResultJson> {
const resultMaps = await this.store.query(query.queryString) as Map<string, any>[];
const sparqlResultJson: SparqlResultJson = {
head: {
vars: []
},
results: {
bindings: []
}
};

// Get the variable names from the first map
const variableNames: string[] = [];
const seenKeys = new Set<string>();
for (const resultMap of resultMaps) {
for (const key of resultMap.keys()) {
if (!seenKeys.has(key)) {
seenKeys.add(key);
variableNames.push(key);
}
}
};
sparqlResultJson.head.vars = variableNames;

// Convert each map to a binding object
for (let resultMap of resultMaps) {
const binding: any = {};
for (let variableName of variableNames) {
const term = resultMap.get(variableName);
if (!term) {
continue;
}
if (term.termType === 'NamedNode') {
binding[variableName] = {
type: "uri",
value: term.value
};
} else if (term.termType === 'BlankNode') {
binding[variableName] = {
type: "bnode",
value: term.value
};
} else if (term.termType === 'Literal') {
const t = {} as any;
t.type = 'literal';
t.value = term.value;
if (term.datatype) {
if (term.datatype.value !== 'http://www.w3.org/2001/XMLSchema#string' && term.datatype.value !== 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString') {
t.datatype = term.datatype.value;
}
}
if (term.language) {
t['xml:lang'] = term.language;
}
binding[variableName] = t;
}
}
sparqlResultJson.results!.bindings.push(binding);
private async _select(query: SparqlQuery): Promise<string> {
const result = await this.#store.query(query.queryString, {
results_format: "json",
});
return result;
};



}
return sparqlResultJson;
}

public async update(query: SparqlQuery): Promise<any> {
return await this.store.update(query.queryString);
return await this.#store.update(query.queryString, {});
}

/**
*
* @param rdfString Load RDF data into the store
* @param mimeType Supported MIME types are: application/n-triples, text/turtle, application/rdf+xml, application/n-quads
* @returns
*/
public load(rdfString: string, mimeType: RdfMimeType): void {
return this.store.load(rdfString, mimeType, null, defaultGraph());
const options = {
format: mimeType,
to_graph_name: defaultGraph()
};

return this.#store.load(rdfString, options, null, null);
}

public async dump(mimeType: RdfMimeType): Promise<any> {
return await this.store.dump(mimeType, defaultGraph());
/**
*
* @param mimeType Supported MIME types are: application/n-triples, text/turtle, application/rdf+xml, application/n-quads
* @returns Serialized RDF data
*/
public async dump(mimeType: RdfMimeType): Promise<string> {
const options = {
format: mimeType,
to_graph_name: defaultGraph()
};
return await this.#store.dump(options, null);
}


/**
* Get the size of the store
* @returns the number of triples in the store
*/
public get size(): number {
return this.store.size;
return this.#store.size;
}

}

0 comments on commit 34b40e0

Please sign in to comment.