Skip to content

Commit

Permalink
feat(OrderBy): add new order by constraint style
Browse files Browse the repository at this point in the history
Fixes #9
  • Loading branch information
jamesfer committed Jun 18, 2018
1 parent f097061 commit 2324831
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 29 deletions.
50 changes: 32 additions & 18 deletions src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from './clauses';
import { DeleteOptions } from './clauses/delete';
import { MatchOptions } from './clauses/match';
import { Direction, OrderConstraints } from './clauses/order-by';
import { Direction, OrderConstraint, OrderConstraints } from './clauses/order-by';
import { PatternCollection } from './clauses/pattern-clause';
import { SetOptions, SetProperties } from './clauses/set';
import { Term } from './clauses/term-list-clause';
Expand Down Expand Up @@ -383,43 +383,57 @@ export abstract class Builder<Q> extends SetBlock<Q> {
* https://neo4j.com/docs/developer-manual/current/cypher/clauses/order-by}
* to the query.
*
* You can supply a single string or an array of strings to order by and the
* direction parameter can control which way all fields are sorted by.
* Pass a single string or an array of strings to order by.
* ```javascript
* query.orderBy([
* 'name',
* 'occupation',
* ], 'DESC')
* ])
* // ORDER BY name, occupation
* ```
*
* Results in a query of
* You can control the sort direction by adding a direction to each property.
* ```javascript
* query.orderBy([
* ['name', 'DESC'],
* 'occupation', // Same as ['occupation', 'ASC']
* ])
* // ORDER BY name DESC, occupation
* ```
* ORDER BY name DESC, occupation DESC
*
* The second parameter is the default search direction for all properties that
* don't have a direction specified. So the above query could instead be
* written as:
* ```javascript
* query.orderBy([
* 'name',
* ['occupation', 'ASC']
* ], 'DESC')
* // ORDER BY name DESC, occupation
* ```
*
* If you would like to control the direction on each property individually,
* you can provide an object where each key is the property and the value is a
* direction. Eg:
* Valid values for directions are `DESC`, `DESCENDING`, `ASC`, `ASCENDING`.
* `true` and `false` are also accepted (`true` being the same as `DESC` and
* `false` the same as `ASC`), however they should be avoided as they are
* quite ambiguous. Directions always default to `ASC` as it does in cypher.
*
* *Depreciation note:*
* It was previously acceptable to pass an object where each key is the
* property and the value is a direction. Eg:
* ```javascript
* query.orderBy({
* name: 'DESC',
* occupation: 'ASC',
* })
* ```
*
* Results in a query of
* ```
* ORDER BY name DESC, occupation
* ```
*
* Direction defaults to `ASC` as it does in cypher.
*
* This has been deprecated as the iteration order of objects is not
* always consistent.
*
* @param {_.Many<string> | OrderConstraints} fields
* @param {Direction} dir
* @returns {Q}
*/
orderBy(fields: Many<string> | OrderConstraints, dir?: Direction) {
orderBy(fields: string | (string | OrderConstraint)[] | OrderConstraints, dir?: Direction) {
return this.continueChainClause(new OrderBy(fields, dir));
}

Expand Down
9 changes: 9 additions & 0 deletions src/clauses/order-by.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,13 @@ describe('OrderBy', () => {
});
expect(query.build()).to.equal('ORDER BY node.prop1 DESC, node.prop2, node.prop3 DESC');
});

it('should support multiple order columns with directions using the array syntax', () => {
const query = new OrderBy([
['node.prop1', 'DESC'],
'node.prop2',
['node.prop3', true],
]);
expect(query.build()).to.equal('ORDER BY node.prop1 DESC, node.prop2, node.prop3 DESC');
});
});
35 changes: 24 additions & 11 deletions src/clauses/order-by.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,46 @@
import { Clause } from '../clause';
import { join, map, Many, isString, isArray, Dictionary, reduce, mapValues, assign } from 'lodash';
import { join, map, isString, isArray, Dictionary, trim } from 'lodash';

export type Direction = boolean | 'DESC' | 'DESCENDING' | 'ASC' | 'ASCENDING';
export type InternalDirection = 'DESC' | '';
export type OrderConstraint = [string, Direction] | [string];
export type InternalOrderConstraint = { field: string, direction: InternalDirection };
export type OrderConstraints = Dictionary<Direction>;

export class OrderBy extends Clause {
constraints: Dictionary<'DESC' | ''>;
constraints: InternalOrderConstraint[];

constructor(fields: Many<string> | OrderConstraints, dir?: Direction) {
constructor(fields: string | (string | OrderConstraint)[] | OrderConstraints, dir?: Direction) {
super();
const reverse = OrderBy.normalizeDirection(dir);
const direction = OrderBy.normalizeDirection(dir);

if (isString(fields)) {
this.constraints = { [fields]: reverse };
this.constraints = [{ direction, field: fields }];
} else if (isArray(fields)) {
this.constraints = reduce(fields, (obj, field) => assign(obj, { [field]: reverse }), {});
this.constraints = map(fields, (field): InternalOrderConstraint => {
if (!isArray(field)) {
return { field, direction };
}
const fieldDirection = field[1] ? OrderBy.normalizeDirection(field[1]) : direction;
return { field: field[0], direction: fieldDirection };
});
} else {
this.constraints = mapValues(fields, OrderBy.normalizeDirection);
// tslint:disable-next-line:max-line-length
console.warn('Warning: Passing constraints to OrderBy using an object is deprecated as the iteration order is not guaranteed. Use an array instead.');
this.constraints = map(fields, (fieldDirection, field) => {
return { field, direction: OrderBy.normalizeDirection(fieldDirection) };
});
}
}

build() {
const contraints = map(this.constraints, (dir, prop) => {
return prop + (dir.length > 0 ? ` ${dir}` : '');
const constraints = map(this.constraints, ({ field, direction }) => {
return trim(`${field} ${direction}`);
});
return 'ORDER BY ' + join(contraints, ', ');
return 'ORDER BY ' + join(constraints, ', ');
}

private static normalizeDirection(dir?: Direction): 'DESC' | '' {
private static normalizeDirection(dir?: Direction | string): InternalDirection {
const isDescending = dir === 'DESC' || dir === 'DESCENDING' || dir === true;
return isDescending ? 'DESC' : '';
}
Expand Down

0 comments on commit 2324831

Please sign in to comment.