Skip to content

Commit

Permalink
Merge pull request #14 from comake/feat/sparql
Browse files Browse the repository at this point in the history
feat/sparql
  • Loading branch information
adlerfaulkner authored Jan 5, 2023
2 parents 6936ffb + 508c255 commit ce5436d
Show file tree
Hide file tree
Showing 8 changed files with 1,076 additions and 1,569 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@comake/skql-js-engine",
"version": "0.13.0",
"version": "0.13.1",
"description": "Standard Knowledge Query Language Javascript Engine",
"keywords": [
"skl",
Expand Down
158 changes: 152 additions & 6 deletions src/storage/sparql/SparqlQueryAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,21 @@ import type {
SparqlGenerator,
AskQuery,
SelectQuery,
Pattern,
Variable,
GraphPattern,
IriTerm,
Ordering,
GroupPattern,
} from 'sparqljs';
import { Generator } from 'sparqljs';
import { toJSValueFromDataType, triplesToJsonld, triplesToJsonldWithFrame } from '../../util/TripleUtil';
import {
countVariable,
entityVariable,
toJSValueFromDataType,
triplesToJsonld,
triplesToJsonldWithFrame,
} from '../../util/TripleUtil';
import type { Entity } from '../../util/Types';
import type { FindOneOptions, FindAllOptions, FindOptionsWhere } from '../FindOptionsTypes';
import type { QueryAdapter, RawQueryResult } from '../QueryAdapter';
Expand Down Expand Up @@ -62,7 +74,25 @@ export class SparqlQueryAdapter implements QueryAdapter {

public async find(options?: FindOneOptions): Promise<Entity | null> {
const queryBuilder = new SparqlQueryBuilder();
const query = queryBuilder.buildEntityQuery({ ...options, limit: 1 });
const selectQueryData = queryBuilder.buildPatternsFromQueryOptions(
entityVariable,
options?.where,
options?.order,
options?.relations,
);
const entitySelectQuery = this.sparqlSelect(
[ entityVariable, ...selectQueryData.variables ],
selectQueryData.where,
selectQueryData.orders,
1,
);
const entitySelectGroupQuery = this.sparqlSelectGroup([ entitySelectQuery ]);
selectQueryData.graphWhere.unshift(entitySelectGroupQuery);
const query = queryBuilder.buildConstructFromEntitySelectQuery(
selectQueryData.graphWhere,
selectQueryData.variables,
options?.select,
);
const generatedQuery = this.sparqlGenerator.stringify(query);
const responseTriples = await this.executeSparqlSelectAndGetData(generatedQuery);
if (responseTriples.length === 0) {
Expand All @@ -72,41 +102,157 @@ export class SparqlQueryAdapter implements QueryAdapter {
return jsonld as Entity;
}

private sparqlSelect(
variables: Variable[],
where: Pattern[],
order: Ordering[],
limit?: number,
offset?: number,
): SelectQuery {
return {
type: 'query',
queryType: 'SELECT',
variables,
where,
order: order.length > 0 ? order : undefined,
limit,
offset,
prefixes: {},
};
}

public async findBy(where: FindOptionsWhere): Promise<Entity | null> {
return this.find({ where });
}

public async findAll(options?: FindAllOptions): Promise<Entity[]> {
const queryBuilder = new SparqlQueryBuilder();
const query = queryBuilder.buildEntityQuery(options);
const selectQueryData = queryBuilder.buildPatternsFromQueryOptions(
entityVariable,
options?.where,
options?.order,
options?.relations,
);
const entitySelectVariables = [ entityVariable, ...selectQueryData.variables ];
const entitySelectQuery = this.sparqlSelect(
entitySelectVariables,
selectQueryData.where,
selectQueryData.orders,
options?.limit,
options?.offset,
);

let orderedEntityVariableValues: string[] | undefined;
if (selectQueryData.orders.length > 0 && options?.limit !== 1) {
// We need to execute the entity select query here first to get ordered results.
const generatedEntitySelectQuery = this.sparqlGenerator.stringify(entitySelectQuery);
const entitySelectResponse =
await this.executeSparqlSelectAndGetData<SelectVariableQueryResult<any>>(generatedEntitySelectQuery);
const valuesByVariable = this.groupByKey(entitySelectResponse);
orderedEntityVariableValues = (valuesByVariable[entityVariable.value] as NamedNode[])
.map((namedNode: NamedNode): string => namedNode.value);
const variableValueFilter = queryBuilder.buildInFilterForVariables(valuesByVariable);
selectQueryData.graphWhere.push(variableValueFilter);
} else {
const entitySelectGroupQuery = this.sparqlSelectGroup([ entitySelectQuery ]);
selectQueryData.graphWhere.unshift(entitySelectGroupQuery);
}
const query = queryBuilder.buildConstructFromEntitySelectQuery(
selectQueryData.graphWhere,
selectQueryData.variables,
options?.select,
);
const generatedQuery = this.sparqlGenerator.stringify(query);
const responseTriples = await this.executeSparqlSelectAndGetData(generatedQuery);
if (responseTriples.length === 0) {
return [];
}
const jsonld = await triplesToJsonld(responseTriples, options?.relations);
const jsonld = await triplesToJsonld(responseTriples, options?.relations, orderedEntityVariableValues);
if (Array.isArray(jsonld)) {
return jsonld as Entity[];
}
return [ jsonld ] as Entity[];
}

private groupByKey(
entitySelectResponse: SelectVariableQueryResult<any>[],
): Record<string, (NamedNode | Literal)[]> {
return entitySelectResponse
.reduce((obj: Record<string, (NamedNode | Literal)[]>, result): Record<string, (NamedNode | Literal)[]> => {
for (const [ key, value ] of Object.entries(result)) {
if (!(key in obj)) {
obj[key] = [ value ];
} else {
obj[key].push(value);
}
}
return obj;
}, {});
}

private sparqlSelectGroup(patterns: Pattern[]): GroupPattern {
return {
type: 'group',
patterns,
};
}

public async findAllBy(where: FindOptionsWhere): Promise<Entity[]> {
return this.findAll({ where });
}

public async exists(where: FindOptionsWhere): Promise<boolean> {
const queryBuilder = new SparqlQueryBuilder();
const query = queryBuilder.buildEntityExistQuery(where);
const selectQueryData = queryBuilder.buildPatternsFromQueryOptions(entityVariable, where);
const query = this.sparqlAsk(selectQueryData.where);
return await this.executeAskQueryAndGetResponse(query);
}

private sparqlAsk(where: Pattern[]): AskQuery {
return {
type: 'query',
queryType: 'ASK',
where,
prefixes: {},
};
}

public async count(where?: FindOptionsWhere): Promise<number> {
const queryBuilder = new SparqlQueryBuilder();
const query = queryBuilder.buildEntityCountQuery(where);
const selectQueryData = queryBuilder.buildPatternsFromQueryOptions(entityVariable, where);
const query = this.sparqlCountSelect(selectQueryData.where, selectQueryData.graphWhere);
return await this.executeSelectCountAndGetResponse(query);
}

private sparqlCountSelect(where: Pattern[], graphWhere: Pattern[]): SelectQuery {
return {
type: 'query',
queryType: 'SELECT',
variables: [{
expression: {
type: 'aggregate',
aggregation: 'count',
distinct: true,
expression: entityVariable,
},
variable: countVariable,
}],
where: [
this.sparqlSelectGraph(entityVariable, where),
...graphWhere,
],
prefixes: {},
};
}

private sparqlSelectGraph(name: Variable | NamedNode, patterns: Pattern[]): GraphPattern {
return {
type: 'graph',
name: name as IriTerm,
patterns,
};
}

public async save(entity: Entity): Promise<Entity>;
public async save(entities: Entity[]): Promise<Entity[]>;
public async save(entityOrEntities: Entity | Entity[]): Promise<Entity | Entity[]> {
Expand Down
Loading

0 comments on commit ce5436d

Please sign in to comment.