Skip to content

Commit

Permalink
Review sorting.
Browse files Browse the repository at this point in the history
  • Loading branch information
RubenVerborgh committed Nov 10, 2019
1 parent 88620cd commit 1cb815c
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 44 deletions.
4 changes: 2 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
object-curly-spacing: [ error, always ],
object-property-newline: off,
one-var: off,
one-var-declaration-per-line: error,
one-var-declaration-per-line: off,
operator-assignment: error,
operator-linebreak: [ error, after, { overrides: { ":": after } } ],
padded-blocks: [ error, never ],
Expand Down Expand Up @@ -221,7 +221,7 @@
no-var: error,
object-shorthand: error,
prefer-arrow-callback: error,
prefer-const: error,
prefer-const: [ error, { destructuring: all } ],
prefer-destructuring: [ error, { array: false, object: true } ],
prefer-numeric-literals: error,
prefer-rest-params: error,
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,16 @@ async function showPerson(person) {
### Sorting path results
```Javascript
(async person => {
for await (const uri of person.interest.sort('label'))
console.log(`- ${uri}`);
for await (const uri of person.interest.sort('label'))
console.log(`- ${uri}`);
})(ruben);

```

The sort function takes multiple arguments,
creating a path that sorts on the last argument.
The path can also continue after the sort.
E.g.: `person.friends.sort('country', 'label').givenName`
The path can also continue after the sort:
`person.friends.sort('country', 'label').givenName`
will sort the friends based on the label of their country,
and then return their names.

Expand Down
6 changes: 4 additions & 2 deletions src/PathExpressionHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export default class PathExpressionHandler {
while (current.parent) {
// Obtain and store predicate
if (current.predicate) {
const predicate = await current.predicate;
segments.unshift({ predicate, sort: current.sort });
segments.unshift({
predicate: await current.predicate,
sort: current.sort,
});
}
// Move to parent link
current = current.parent;
Expand Down
12 changes: 8 additions & 4 deletions src/PathProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,14 @@ export default class PathProxy {
if (!path.extendPath) {
const pathProxy = this;
path.extendPath = function extendPath(newData, parent = this) {
// Data that needs to be given to followup proxies in the path
if (parent && parent.childData && parent.childData.count > 0) {
const { count: cCount, data: cData } = parent.childData;
newData = Object.assign(cCount > 1 ? { childData: { count: cCount - 1, data: cData } } : {}, cData, newData);
// Inherit data from parent if present
const inheritedData = parent && parent.childData;
if (inheritedData) {
let { childLimit, ...inherited } = inheritedData;
newData = { ...inherited, ...newData };
// Only inherit data for up to childLimit children
if (--childLimit > 0)
newData.childData = { childLimit, ...inherited };
}
return pathProxy.createPath(settings, { parent, extendPath, ...newData });
};
Expand Down
12 changes: 7 additions & 5 deletions src/SortHandler.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* Returns a function that creates a new path with the same values as the previous one but sorted on the given predicate.
* Returns a function that creates a new path with the same values,
* but sorted on the given predicate.
* The function accepts multiple parameters to sort on a deeper path.
*/
export default class SortHandler {
Expand All @@ -8,11 +9,12 @@ export default class SortHandler {
}

handle(pathData) {
return (...args) => {
return (...properties) => {
if (pathData.sort)
throw new Error('Multiple sorts not supported');
const sortProxy = pathData.extendPath({ childData: { count: args.length, data: { sort: this.order } } });
return args.reduce((acc, val) => acc[val], sortProxy);
throw new Error('Multiple sorts on a path are not yet supported');
const childData = { childLimit: properties.length, sort: this.order };
const sortPath = pathData.extendPath({ childData });
return properties.reduce((path, property) => path[property], sortPath);
};
}
}
34 changes: 16 additions & 18 deletions src/SparqlHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,21 @@ export default class SparqlHandler {
throw new Error(`${pathData} should at least contain a subject and a predicate`);

// Create triple patterns
let queryVar = '?subject';
let sortVar;
let clauses = [];
let queryVar = '?subject', sorts = [], clauses = [];
if (pathExpression.length > 1) {
queryVar = this.createVar(pathData.property);
({ queryVar, sortVar, clauses } = this.expressionToTriplePatterns(pathExpression, queryVar));
({ queryVar, sorts, clauses } = this.expressionToTriplePatterns(pathExpression, queryVar));
}
if (pathData.finalClause)
clauses.push(pathData.finalClause(queryVar));

// Create SPARQL query body
const distinct = pathData.distinct ? 'DISTINCT ' : '';
const select = `SELECT ${distinct}${pathData.select ? pathData.select : queryVar}`;
const where = `WHERE {\n ${clauses.join('\n ')}\n}`;
const sort = (sortVar && sortVar.v) ? ` \nORDER BY ${sortVar.order}(${sortVar.v})` : '';
return `${select} ${where}${sort}`;
const where = ` WHERE {\n ${clauses.join('\n ')}\n}`;
const orderClauses = sorts.map(({ order, variable }) => `${order}(${variable})`);
const orderBy = orderClauses.length === 0 ? '' : `\nORDER BY ${orderClauses.join(' ')}`;
return `${select}${where}${orderBy}`;
}

mutationExpressionToQuery({ mutationType, conditions, predicate, objects }) {
Expand Down Expand Up @@ -80,30 +79,29 @@ export default class SparqlHandler {
}

expressionToTriplePatterns([root, ...pathExpression], lastVar, scope = {}) {
const last = pathExpression.length - 1;
const lastIndex = pathExpression.length - 1;
let object = this.termToString(skolemize(root.subject));
const sortVar = { v: undefined, order: '' };
let queryVar = object;
const sorts = [];
const clauses = pathExpression.map((segment, index) => {
// Obtain components and generate triple pattern
const subject = object;
const { predicate } = segment;
object = index < last ? this.createVar(`v${index}`, scope) : lastVar;
object = index < lastIndex ? this.createVar(`v${index}`, scope) : lastVar;
const result = `${subject} ${this.termToString(predicate)} ${object}.`;
// Update sort var if required
if (segment.sort) {
sortVar.v = object;
sortVar.order = segment.sort.toUpperCase();

// Reset followup non-sort patterns to start from correct subject again
object = queryVar;
// If the sort option was not set, use this object as a query variable
if (!segment.sort) {
queryVar = object;
}
// If sort was set, use this object as a sorting variable
else {
queryVar = object;
sorts.push({ variable: object, order: segment.sort });
object = queryVar;
}
return result;
});
return { queryVar, sortVar, clauses };
return { queryVar, sorts, clauses };
}

// Creates a unique query variable within the given scope, based on the suggestion
Expand Down
10 changes: 5 additions & 5 deletions test/unit/PathProxy-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,17 +347,17 @@ describe('a PathProxy with child data', () => {
pathProxy = new PathProxy({ handlers });
});

it('will give the data to grandchildren for count > 1', () => {
const path = pathProxy.createPath(settings, { a: 1, childData: { count: 2, data: { b: 6 } } });
it('will pass the data to grandchildren for count > 1', () => {
const path = pathProxy.createPath(settings, { a: 1, childData: { childLimit: 2, b: 6 } });
const extendedPath = path.internal.extendPath({});
expect(extendedPath.internal).toHaveProperty('b');
expect(extendedPath.internal.b).toBe(6);
expect(extendedPath.internal).toHaveProperty('childData');
expect(extendedPath.internal.childData).toEqual({ count: 1, data: { b: 6 } });
expect(extendedPath.internal.childData).toEqual({ childLimit: 1, b: 6 });
});

it('will not give the childData property to children if count <= 1', () => {
const path = pathProxy.createPath(settings, { a: 1, childData: { count: 1, data: { b: 6 } } });
it('will not pass the childData property to children if childLimit <= 1', () => {
const path = pathProxy.createPath(settings, { a: 1, childData: { childLimit: 1, b: 6 } });
const extendedPath = path.internal.extendPath({});
expect(extendedPath.internal).toHaveProperty('b');
expect(extendedPath.internal.b).toBe(6);
Expand Down
7 changes: 5 additions & 2 deletions test/unit/SortHandler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ describe('a SortHandler instance', () => {
});

it('errors if called after a previous sort', () => {
expect(handler.handle({ sort: 'DESC' })).toThrow('Multiple sorts not supported');
expect(handler.handle({ sort: 'DESC' })).toThrow('Multiple sorts on a path are not yet supported');
});

it('creates a path entry with child data', () => {
expect(handler.handle({ extendPath })()).toEqual({ ...dummyData, childData: { count: 0, data: { sort: 'ASC' } } });
expect(handler.handle({ extendPath })()).toEqual({
...dummyData,
childData: { childLimit: 0, sort: 'ASC' },
});
});

it('calls the proxy with the given parameters', () => {
Expand Down
22 changes: 20 additions & 2 deletions test/unit/SparqlHandler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ describe('a SparqlHandler instance', () => {
/<urn:ldflex:sk\d+> <https:\/\/ex.org\/p1> \?p1\./);
});

it('adds ORDER BY when required', async () => {
it('supports ORDER BY with one variable', async () => {
const pathExpression = [
{ subject: namedNode('https://example.org/#me') },
{ predicate: namedNode('https://ex.org/p1') },
Expand All @@ -148,9 +148,27 @@ describe('a SparqlHandler instance', () => {
SELECT ?v0 WHERE {
<https://example.org/#me> <https://ex.org/p1> ?v0.
?v0 <https://ex.org/p2> ?p2.
}
}
ORDER BY ASC(?p2)`));
});

it('supports ORDER BY with two variables', async () => {
const pathExpression = [
{ subject: namedNode('https://example.org/#me') },
{ predicate: namedNode('https://ex.org/p1') },
{ predicate: namedNode('https://ex.org/p2'), sort: 'ASC' },
{ predicate: namedNode('https://ex.org/p3'), sort: 'ASC' },
];

const pathData = { property: 'p3' };
expect(await handler.handle(pathData, { pathExpression })).toEqual(deindent(`
SELECT ?v0 WHERE {
<https://example.org/#me> <https://ex.org/p1> ?v0.
?v0 <https://ex.org/p2> ?v1.
?v0 <https://ex.org/p3> ?p3.
}
ORDER BY ASC(?v1) ASC(?p3)`));
});
});

describe('with mutationExpressions', () => {
Expand Down

0 comments on commit 1cb815c

Please sign in to comment.