sparnatural
Version:
Visual client-side SPARQL query builder and knowledge graph exploration tool
249 lines (228 loc) • 8.41 kB
text/typescript
import { Order } from "../json/ISparJson";
import ISparnaturalSpecification from "../../spec-providers/ISparnaturalSpecification";
import {
Grouping,
Ordering,
SelectQuery,
Variable,
VariableExpression,
VariableTerm,
} from "sparqljs";
import SparnaturalComponent from "../../components/SparnaturalComponent";
import WhereBuilder from "./WhereBuilder";
import SparqlFactory from "./SparqlFactory";
import { DataFactory } from "rdf-data-factory";
import { DraggableComponentState } from "../../components/variables-section/variableorder/DraggableComponent";
import GroupWrapper from "../../components/builder-section/groupwrapper/GroupWrapper";
import ISpecificationProperty from "../../spec-providers/ISpecificationProperty";
import SparnaturalFormComponent from "../../../sparnatural-form/components/SparnaturalFormComponent";
const factory = new DataFactory();
/*
Reads out the UI and creates the and sparqljs pattern.
sparqljs pattern builds pattern structure on top of rdfjs datamodel. see:https://rdf.js.org/data-model-spec/
It goes recursively through all the grpWrappers and reads out their values.
*/
export default class SparqlGenerator {
typePredicate: string;
specProvider: ISparnaturalSpecification;
prefixes: { [key: string]: string } = {};
sparnatural: SparnaturalComponent;
defaultLabelVars: Variable[] = []; // see: #checkForDefaultLabel()
settings: any;
constructor(
sparnatural: SparnaturalComponent,
specProvider: ISparnaturalSpecification,
prefixes: { [key: string]: string } = {},
settings: any
) {
this.sparnatural = sparnatural;
this.specProvider = specProvider;
this.prefixes = prefixes;
this.settings = settings;
}
generateQuery(
variables: Array<DraggableComponentState>,
order: Order,
distinct: boolean,
limit: number
): SelectQuery {
const sparqlJsQuery: SelectQuery = {
queryType: "SELECT",
distinct: distinct,
variables: this.#varsToRDFJS(variables),
type: "query",
where: this.#createWhereClause(),
prefixes: this.prefixes,
order: this.#orderToRDFJS(
order,
factory.variable(variables[0].selectedVariable.variable)
),
limit: limit && limit > 0 ? limit : undefined,
};
// if the RdfJsQuery contains empty 'where' array, then the generator crashes.
// create query with no triples
if (sparqlJsQuery.where?.length === 0) {
sparqlJsQuery.where = [
{
type: "bgp",
triples: [],
},
];
}
// post processing for defaultlabel property
if (this.defaultLabelVars.length > 0) {
this.defaultLabelVars.forEach((defaultLabelVar) =>
this.#insertDefaultLabelVar(sparqlJsQuery, defaultLabelVar)
);
}
// don't set an order if there is no expression for it
if (!sparqlJsQuery?.order || !sparqlJsQuery?.order[0]?.expression)
delete sparqlJsQuery.order;
// set a GROUP BY based on aggregation expression in the variables
// add this after defaultLabel var have been inserted, and re-read them from the query
sparqlJsQuery.group = this.#addGroupBy(
sparqlJsQuery.variables as Variable[]
);
return sparqlJsQuery;
}
/**
* @param variables The list variables of the SELECT query
* @returns GROUP BY clause if needed, of all non-aggregated variables, or undefined if not needed
*/
#addGroupBy(variables: Variable[]): Grouping[] {
if (this.#needsGrouping(variables)) {
let g: Grouping[] = [];
variables.forEach((v) => {
if (!(v as VariableExpression).expression) {
g.push({
expression: v as VariableTerm,
});
}
});
return g;
} else {
// no aggregation, or only one column, grouping is undefined
return undefined;
}
}
#needsGrouping(variables: Variable[]): boolean {
return variables.find((v) => (v as VariableExpression).expression) &&
variables.length > 1
? true
: false;
}
#createWhereClause() {
if (this.sparnatural instanceof SparnaturalComponent) {
const builder = new WhereBuilder(
this.sparnatural.BgWrapper.componentsList.rootGroupWrapper,
this.specProvider,
this.typePredicate,
false,
false,
this.settings
);
builder.build();
this.defaultLabelVars = builder.getDefaultVars();
if (builder.getExecutedAfterPtrns().length > 0) {
// put all normal patterns in a subquery to garantee they are logically executed before
// and their variables are bound in the rest of the query outside the subquery
let subquery = SparqlFactory.buildSubQuery(builder.getResultPtrns());
// and the patterns to be executed after in the normal where clause
return [subquery, ...builder.getExecutedAfterPtrns()];
} else {
return builder.getResultPtrns();
}
}
}
#varsToRDFJS(variables: Array<DraggableComponentState>): Variable[] {
let variablesArray: Variable[][] = variables.map((v) => {
if (v.aggregateFunction) {
return [
SparqlFactory.buildAggregateFunctionExpression(
v.aggregateFunction,
factory.variable(v.originalVariable.variable),
factory.variable(v.selectedVariable.variable)
),
];
} else {
// find where the variable comes from
let specProperty: ISpecificationProperty = undefined;
this.sparnatural.BgWrapper.componentsList.rootGroupWrapper.traversePreOrder(
(grpWrapper: GroupWrapper) => {
if (
grpWrapper.CriteriaGroup.EndClassGroup.endClassVal.variable ==
v.selectedVariable.variable
) {
// check if the spec tells us that begin date / end date / exact date propeties are used
let propertyType =
grpWrapper.CriteriaGroup.ObjectPropertyGroup.getTypeSelected();
console.log("propertyType: ", propertyType);
specProperty = this.specProvider.getProperty(propertyType);
console.log("specProperty: ", specProperty);
}
}
); // end traverse
// not found as an object, we cannot read the specification property, so return as normal
if (!specProperty) {
return [factory.variable(v.selectedVariable.variable)];
}
if (
specProperty.getBeginDateProperty() &&
specProperty.getEndDateProperty()
) {
let result: Variable[] = [];
if (specProperty.getBeginDateProperty()) {
result.push(
factory.variable(v.selectedVariable.variable + "_begin")
);
}
if (specProperty.getEndDateProperty()) {
result.push(factory.variable(v.selectedVariable.variable + "_end"));
}
if (specProperty.getExactDateProperty()) {
result.push(
factory.variable(v.selectedVariable.variable + "_exact")
);
}
return result;
} else {
// no begin or date, return as normal
return [factory.variable(v.selectedVariable.variable)];
}
}
});
let finalResult: Variable[] = [];
variablesArray.forEach((varArray: Variable[]) => {
finalResult.push(...varArray);
});
return finalResult;
}
// It will be ordered by the Provided variable
#orderToRDFJS(order: Order, variable: VariableTerm): Ordering[] {
if (order == Order.DESC || order == Order.ASC) {
return [
{
expression: variable,
descending: order == Order.DESC ? true : false,
},
];
} else {
// no order
return null;
}
}
// inserts the default label var right after the variable of the same name
#insertDefaultLabelVar(sparqlQuery: SelectQuery, defaultLabelVar: Variable) {
var originalVar = (defaultLabelVar as VariableTerm).value.substring(
0,
(defaultLabelVar as VariableTerm).value.length - "_label".length
);
for (var i = 0; i < sparqlQuery.variables.length; i++) {
if ((sparqlQuery.variables[i] as VariableTerm).value == originalVar) {
sparqlQuery.variables.splice(i + 1, 0, defaultLabelVar);
// don't forget, otherwise infinite loop
break;
}
}
}
}