Skip to content

Commit

Permalink
Merge pull request #51 from comake/groupBySupport
Browse files Browse the repository at this point in the history
Group by support
  • Loading branch information
jagzmz authored Oct 25, 2024
2 parents dc5e56b + 8ceb39e commit 9769dce
Show file tree
Hide file tree
Showing 9 changed files with 389 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
19 changes: 10 additions & 9 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@comake/skl-js-engine",
"version": "0.21.0",
"version": "0.22.0",
"description": "Standard Knowledge Language Javascript Engine",
"keywords": [
"skl",
Expand Down Expand Up @@ -83,7 +83,7 @@
"eslint-plugin-tsdoc": "^0.2.16",
"eslint-plugin-unused-imports": "^2.0.0",
"fs": "^0.0.1-security",
"husky": "^8.0.0",
"husky": "^8.0.3",
"jest": "^29.5.0",
"jsdom": "^20.0.0",
"ts-jest": "^29.1.0",
Expand Down
5 changes: 5 additions & 0 deletions src/SklEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
ensureArray,
} from './util/Util';
import { SKL, SHACL, RDFS, SKL_ENGINE, XSD, RDF } from './util/Vocabularies';
import { GroupByOptions, GroupByResponse } from './storage/GroupOptionTypes';

Check failure on line 56 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

All imports in the declaration are only used as types. Use `import type`

Check failure on line 56 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

`./storage/GroupOptionTypes` import should occur before import of `./storage/operator/Exists`

export type VerbHandler = <T extends OrArray<NodeObject> = OrArray<NodeObject>>(
params: JSONObject,
Expand Down Expand Up @@ -131,6 +132,10 @@ export class SKLEngine {
return await this.queryAdapter.findAll(options);
}

public async groupBy(options: GroupByOptions): Promise<GroupByResponse> {
return await this.queryAdapter.groupBy(options);
}

public async findAllBy(where: FindOptionsWhere): Promise<Entity[]> {
return await this.queryAdapter.findAllBy(where);
}
Expand Down
32 changes: 32 additions & 0 deletions src/storage/GroupOptionTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FindOptionsWhere } from "./FindOptionsTypes";

Check failure on line 1 in src/storage/GroupOptionTypes.ts

View workflow job for this annotation

GitHub Actions / lint

All imports in the declaration are only used as types. Use `import type`

Check failure on line 1 in src/storage/GroupOptionTypes.ts

View workflow job for this annotation

GitHub Actions / lint

Strings must use singlequote

// Add these types at the top of the file
export interface GroupByOptions {
where?: FindOptionsWhere;
groupBy?: string[];
dateRange?: {
start: string;
end: string;
};
dateGrouping?: "month" | "day";

Check failure on line 11 in src/storage/GroupOptionTypes.ts

View workflow job for this annotation

GitHub Actions / lint

Strings must use singlequote

Check failure on line 11 in src/storage/GroupOptionTypes.ts

View workflow job for this annotation

GitHub Actions / lint

Strings must use singlequote
limit?: number;
offset?: number;
}

export interface GroupResult {
group: Record<string, string | number>;
count: number;
entityIds: string[];
}

export interface GroupByResponse {
results: GroupResult[];
meta: {
totalCount: number;
dateRange?: {
start: string;
end: string;
};
groupings: string[];
};
}
5 changes: 5 additions & 0 deletions src/storage/query-adapter/QueryAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
FindOneOptions,
FindOptionsWhere,
} from '../FindOptionsTypes';
import { GroupByOptions, GroupByResponse } from '../GroupOptionTypes';

Check failure on line 12 in src/storage/query-adapter/QueryAdapter.ts

View workflow job for this annotation

GitHub Actions / lint

All imports in the declaration are only used as types. Use `import type`

export type RawQueryResult = Record<string, number | boolean | string>;

Expand Down Expand Up @@ -102,4 +103,8 @@ export interface QueryAdapter {
* Removes all entities from the database.
*/
destroyAll(): Promise<void>;
/**
* Groups entities by a given options.
*/
groupBy(options: GroupByOptions): Promise<GroupByResponse>;
}
53 changes: 53 additions & 0 deletions src/storage/query-adapter/sparql/SparqlQueryAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type { QueryExecutor } from './query-executor/SparqlQueryExecutor';
import type { SparqlQueryAdapterOptions } from './SparqlQueryAdapterOptions';
import { SparqlQueryBuilder } from './SparqlQueryBuilder';
import { SparqlUpdateBuilder } from './SparqlUpdateBuilder';
import { GroupByOptions, GroupByResponse, GroupResult } from '../../GroupOptionTypes';

Check failure on line 40 in src/storage/query-adapter/sparql/SparqlQueryAdapter.ts

View workflow job for this annotation

GitHub Actions / lint

All imports in the declaration are only used as types. Use `import type`

Check failure on line 40 in src/storage/query-adapter/sparql/SparqlQueryAdapter.ts

View workflow job for this annotation

GitHub Actions / lint

`../../GroupOptionTypes` import should occur before import of `../QueryAdapter`

/**
* A {@link QueryAdapter} that stores data in a database through a sparql endpoint.
Expand Down Expand Up @@ -213,6 +214,58 @@ export class SparqlQueryAdapter implements QueryAdapter {
return entityOrEntities;
}

public async groupBy(options: GroupByOptions): Promise<GroupByResponse> {
const queryBuilder = new SparqlQueryBuilder();
const { query: selectQuery, variableMapping } = await queryBuilder.buildGroupByQuery(options);
const results = await this.queryExecutor.executeSparqlSelectAndGetData(
selectQuery

Check failure on line 221 in src/storage/query-adapter/sparql/SparqlQueryAdapter.ts

View workflow job for this annotation

GitHub Actions / lint

Missing trailing comma
);

// Create reverse mapping from path to variable name
const reverseMapping = Object.entries(variableMapping).reduce((acc, [varName, path]) => {
acc[path] = varName;
return acc;
}, {} as Record<string, string>);

// Transform results
const groupResults: GroupResult[] = results.map((result) => {
const group: Record<string, string | number> = {};

options.groupBy?.forEach((path) => {
const varName = reverseMapping[path];
if (!varName) {
throw new Error(`No variable mapping found for path: ${path}`);
}
const value = result[varName].value;
// Try to convert to number if possible
group[path] = isNaN(Number(value)) ? value : Number(value);
});

if (options.dateGrouping) {
const dateGroupVarName = reverseMapping['dateGroup'];
group.dateGroup = result[dateGroupVarName].value;
}

const countVarName = reverseMapping['count'];
const entityIdsVarName = reverseMapping['entityIds'];

return {
group,
count: parseInt(result[countVarName].value, 10),
entityIds: result[entityIdsVarName].value.split(" "),
};
});

return {
results: groupResults,
meta: {
totalCount: groupResults.reduce((sum, curr) => sum + curr.count, 0),
dateRange: options.dateRange,
groupings: options.groupBy || [],
},
};
}

public async update(id: string, attributes: Partial<Entity>): Promise<void>;
public async update(ids: string[], attributes: Partial<Entity>): Promise<void>;
public async update(idOrIds: string | string[], attributes: Partial<Entity>): Promise<void> {
Expand Down
Loading

0 comments on commit 9769dce

Please sign in to comment.