Skip to content

Commit

Permalink
feat(datacube): get extra metadata on datacubes
Browse files Browse the repository at this point in the history
  • Loading branch information
vhf committed Oct 21, 2019
1 parent 0ecf6d2 commit 8ee33f9
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 53 deletions.
6 changes: 3 additions & 3 deletions __tests__/__snapshots__/serialization.test.ts.snap

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions examples/extra-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// run this example with: $ ts-node examples/language-support.ts
import { inspect } from "util";
import { DataCubeEntryPoint } from "../src/entrypoint";

function prettyPrint(obj) {
return inspect(obj, false, 10000, true);
}
function printTitle(str) {
return `\n\n---- ${str} ----\n`;
}

(async () => {
// instantiate an RDF Data Cube
const entryPoint = new DataCubeEntryPoint(
"https://trifid-lindas.test.cluster.ldbar.ch/query",
{
languages: ["fr", "de"],
extraMetadata: [
{ variable: "contact", iri: "https://pcaxis.described.at/contact", multilang: true },
{ variable: "source", iri: "https://pcaxis.described.at/source", multilang: true },
{ variable: "survey", iri: "https://pcaxis.described.at/survey", multilang: true },
{ variable: "database", iri: "https://pcaxis.described.at/database", multilang: true },
{ variable: "unit", iri: "https://pcaxis.described.at/unit", multilang: true },
{ variable: "note", iri: "https://pcaxis.described.at/note", multilang: true },

{ variable: "dateCreated", iri: "http://schema.org/dateCreated", multilang: false },
{ variable: "dateModified", iri: "http://schema.org/dateModified" },
{ variable: "temporalCoverage", iri: "http://schema.org/temporalCoverage", multilang: true },
{ variable: "description", iri: "http://www.w3.org/2000/01/rdf-schema#comment", multilang: true },
],
},
);
// find all its dataCubes
const dataCubes = await entryPoint.dataCubes();

console.log(prettyPrint(dataCubes));
})();
29 changes: 22 additions & 7 deletions src/datacube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import namespace from "@rdfjs/namespace";
import { Literal, NamedNode, Term } from "rdf-js";
import { Generator as SparqlGenerator } from "sparqljs";
import { Attribute, Component, Dimension, Measure } from "./components";
import { EntryPointOptions } from "./entrypoint";
import { BaseOptions } from "./entrypoint";
import { Query, QueryOptions } from "./query";
import { generateLangCoalesce, generateLangOptionals, prefixes } from "./queryutils";
import { generateLangCoalesce, generateLangOptionals, labelPredicate, literalFromJSON } from "./queryutils";
import { literalToJSON, prefixes, SerializedLiteral } from "./queryutils";
import { SparqlFetcher } from "./sparqlfetcher";
import { SelectQuery } from "./sparqljs";

Expand All @@ -28,6 +29,7 @@ type SerializedDataCube = {
graphIri: string,
labels: Label[],
languages: string[],
extraMetadata: { [key: string]: SerializedLiteral },
components: {
dimensions: string[],
measures: string[],
Expand All @@ -51,10 +53,11 @@ export interface Label {
language: string;
}

export interface DataCubeOptions extends EntryPointOptions {
export interface DataCubeOptions extends BaseOptions {
iri: NamedNode;
labels?: Label[];
graphIri: NamedNode;
labels?: Label[];
extraMetadata?: Map<string, any>;
}

const cube = namespace("http://purl.org/linked-data/cube#");
Expand All @@ -73,6 +76,10 @@ export class DataCube {
graphIri: namedNode(obj.graphIri),
labels: obj.labels,
languages: obj.languages,
extraMetadata: Object.entries(obj.extraMetadata || {}).reduce((acc, [key, serializedLit]) => {
acc.set(key, literalFromJSON(serializedLit));
return acc;
}, new Map()),
});
["dimensions", "measures", "attributes"].forEach((componentTypes) => {
dataCube.cachedComponents[componentTypes] = obj.components[componentTypes]
Expand All @@ -89,6 +96,7 @@ export class DataCube {
public iri: string;
public endpoint: string;
public graphIri?: string;
public extraMetadata: Map<string, Literal>;
private languages: string[];
private fetcher: SparqlFetcher;
private componentsLoaded: boolean = false;
Expand All @@ -107,13 +115,14 @@ export class DataCube {
endpoint: string,
options: DataCubeOptions,
) {
const { iri, labels, graphIri } = options;
const { iri, labels, graphIri, extraMetadata } = options;
this.fetcher = new SparqlFetcher(endpoint);
this.endpoint = endpoint;
this.iri = iri.value;
this.graphIri = graphIri.value;
this.labels = labels || [];
this.languages = options.languages || [];
this.extraMetadata = extraMetadata;
this.cachedComponents = {
dimensions: new Map(),
measures: new Map(),
Expand All @@ -132,12 +141,18 @@ export class DataCube {
.map((component) => JSON.parse(component.toJSON()));
const attributes = Array.from(this.cachedComponents.attributes.values())
.map((component) => JSON.parse(component.toJSON()));
const extraMetadata = Array.from(this.extraMetadata.entries())
.reduce((acc, [key, lit]) => {
acc[key] = literalToJSON(lit);
return acc;
}, {});
const obj: SerializedDataCube = {
endpoint: this.endpoint,
iri: this.iri,
graphIri: this.graphIri,
labels: this.labels,
languages: this.languages,
extraMetadata,
components: {
dimensions,
measures,
Expand Down Expand Up @@ -231,7 +246,7 @@ export class DataCube {
},
],
},
...generateLangOptionals(binding, labelBinding, this.languages),
...generateLangOptionals(binding, labelBinding, labelPredicate, this.languages),
generateLangCoalesce(labelBinding, this.languages),
],
type: "query",
Expand Down Expand Up @@ -382,7 +397,7 @@ export class DataCube {
],
},
},
...generateLangOptionals(binding, labelBinding, this.languages),
...generateLangOptionals(binding, labelBinding, labelPredicate, this.languages),
generateLangCoalesce(labelBinding, this.languages),
],
type: "query",
Expand Down
74 changes: 68 additions & 6 deletions src/entrypoint.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
import { namedNode, variable } from "@rdfjs/data-model";
import { NamedNode } from "rdf-js";
import { NamedNode, Variable } from "rdf-js";
import { Generator as SparqlGenerator } from "sparqljs";
import { DataCube, DataCubeOptions, Label } from "./datacube";
import { generateLangCoalesce, generateLangOptionals, prefixes } from "./queryutils";
import { generateLangCoalesce, generateLangOptionals, labelPredicate, prefixes } from "./queryutils";
import { SparqlFetcher, SparqlFetcherOptions } from "./sparqlfetcher";
import { SelectQuery } from "./sparqljs";
import { BindPattern, BlockPattern, SelectQuery } from "./sparqljs";

export interface EntryPointOptions {
interface ExtraMetadatum {
variable: string;
iri: string;
multilang?: boolean;
}

interface ExtraMetadata {
variables: Variable[];
iris: NamedNode[];
isMultilang: boolean[];
langOptionals: (variable: Variable) => BlockPattern[];
langCoalesce: () => BindPattern[];
}

export interface BaseOptions {
languages?: string[];
fetcher?: SparqlFetcherOptions;
}

export interface EntryPointOptions extends BaseOptions {
extraMetadata?: ExtraMetadatum[];
}

export type SerializedDataCubeEntryPoint = {
endpoint: string,
languages: string[],
dataCubes: string[],
};

/**
* ignore
*/
function flatten<T>(items: T[][]): T[] {
return items.reduce((prev, next) => prev.concat(next), []);
}

export class DataCubeEntryPoint {
/**
* Deserializes a DataCubeEntryPoint from JSON generated by DataCubeEntryPoint#toJSON
Expand All @@ -41,6 +66,7 @@ export class DataCubeEntryPoint {
private allDataCubesLoaded: boolean = false;
private cachedGraphs: NamedNode[];
private graphsLoaded: boolean = false;
private extraMetadata: ExtraMetadata;

/**
* A DataCubeEntryPoint queries a SPARQL endpoint and retrieves [[DataCube]]s and
Expand All @@ -57,6 +83,33 @@ export class DataCubeEntryPoint {
this.fetcher = new SparqlFetcher(endpoint, options.fetcher || {});
this.cachedDataCubes = new Map();
this.cachedGraphs = [];

this.extraMetadata = {
variables: [],
iris: [],
isMultilang: [],
langOptionals: (iriBinding) => {
return flatten(this.extraMetadata.variables.map((variab, index) => {
const predicate = this.extraMetadata.iris[index];
const isMultilang = this.extraMetadata.isMultilang[index];
const languages = isMultilang ? this.languages : null;
return generateLangOptionals(iriBinding, variab, predicate, languages);
}));
},
langCoalesce: () => {
return this.extraMetadata.variables
.filter((metadata, index) => this.extraMetadata.isMultilang[index])
.map((variab, index) => {
return generateLangCoalesce(variab, this.languages);
});
},
};
const extraMetadata = options.extraMetadata || [];
extraMetadata.forEach((metadata) => {
this.extraMetadata.variables.push(variable(metadata.variable));
this.extraMetadata.iris.push(namedNode(metadata.iri));
this.extraMetadata.isMultilang.push(Boolean(metadata.multilang));
});
}

/**
Expand Down Expand Up @@ -153,13 +206,19 @@ export class DataCubeEntryPoint {
}

private cacheDataCubes(dataCubes: Array<{ iri: NamedNode, label: Label, graphIri: string }>) {
const dataCubesByIri = dataCubes.reduce((acc, { iri, label, graphIri }) => {
const dataCubesByIri = dataCubes.reduce((acc, obj) => {
const extraMetadata = new Map();
this.extraMetadata.variables.forEach((variab) => {
extraMetadata.set(variab.value, obj[variab.value]);
});
const { iri, label, graphIri } = obj;
if (!acc[iri.value]) {
acc[iri.value] = {
iri,
labels: [],
graphIri,
languages: this.languages,
extraMetadata,
};
}
acc[iri.value].labels.push({
Expand Down Expand Up @@ -187,6 +246,7 @@ export class DataCubeEntryPoint {
iriBinding,
graphIriBinding,
labelBinding,
...this.extraMetadata.variables,
],
where: [
{
Expand All @@ -203,10 +263,12 @@ export class DataCubeEntryPoint {
},
],
},
...generateLangOptionals(iriBinding, labelBinding, this.languages),
...generateLangOptionals(iriBinding, labelBinding, labelPredicate, this.languages),
...this.extraMetadata.langOptionals(iriBinding),
],
},
generateLangCoalesce(labelBinding, this.languages),
...this.extraMetadata.langCoalesce(),
],
type: "query",
};
Expand Down
8 changes: 4 additions & 4 deletions src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { Literal, NamedNode, Variable } from "rdf-js";
import { Generator as SparqlGenerator } from "sparqljs";
import { Component } from "./components";
import { DataCube } from "./datacube";
import { EntryPointOptions } from "./entrypoint";
import { BaseOptions } from "./entrypoint";
import { IExpr, Operator } from "./expressions";
import { combineFilters, createOperationExpression, prefixes } from "./queryutils";
import { combineFilters, createOperationExpression, labelPredicate, prefixes } from "./queryutils";
import { generateLangCoalesce, generateLangOptionals } from "./queryutils";
import { SparqlFetcher } from "./sparqlfetcher";
import { BgpPattern, FilterPattern, Ordering, SelectQuery, VariableExpression, Wildcard } from "./sparqljs";
Expand All @@ -32,7 +32,7 @@ interface QueryState {
}

// tslint:disable-next-line: no-empty-interface
export interface QueryOptions extends EntryPointOptions {}
export interface QueryOptions extends BaseOptions {}

/**
* @ignore
Expand Down Expand Up @@ -489,7 +489,7 @@ export class Query {
});

const labelBinding = variable(`${bindingName}Label`);
const langOptional = generateLangOptionals(binding, labelBinding, this.languages);
const langOptional = generateLangOptionals(binding, labelBinding, labelPredicate, this.languages);
const langCoalesce = generateLangCoalesce(labelBinding, this.languages);
fetchLabels.push(...langOptional, langCoalesce);
query.variables.push(binding, labelBinding);
Expand Down
Loading

0 comments on commit 8ee33f9

Please sign in to comment.