From 362c7ca960f3a3421b3abbd59e6522c2802a2b09 Mon Sep 17 00:00:00 2001 From: James Ferguson Date: Wed, 8 Aug 2018 21:31:29 +1000 Subject: [PATCH] feat(Where): allow RegExp objects directly in where clause Javascript RegExp objects can now be passed into the regexp comparator and used directly as a value in Where clauses. Closes #13 --- src/builder.ts | 15 +++++++++++++++ src/clauses/where-comparators.ts | 24 ++++++++++++++++++------ src/clauses/where-utils.ts | 6 +++++- src/clauses/where.spec.ts | 16 ++++++++++++++++ 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/builder.ts b/src/builder.ts index 055b055e..d1d6735b 100644 --- a/src/builder.ts +++ b/src/builder.ts @@ -600,6 +600,21 @@ export abstract class Builder extends SetBlock { * // WHERE (name = 'Alan' OR name = 'Steve' OR name = 'Barry') AND age = 54 * ``` * + * For convenience you can also pass a Javascript RegExp object as a value, + * which will then be converted into a string before it is passed to cypher. + * *However*, beware that, the cypher regexp syntax is inherited from + * [java]{@link + * https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html}, + * and may have slight differences to the Javascript syntax. If you would + * prefer, you can use the `regexp` comparator and use strings instead of + * RegExp objects. For example, Javascript RegExp flags will not be + * preserved when sent to cypher. + * ```javascript + * query.where({ + * name: /[A-Z].*son/, + * }) + * // WHERE age =~ '[A-Z].*son' + * * For more complex comparisons, you can use the comparator functions such as: * ```javascript * query.where({ diff --git a/src/clauses/where-comparators.ts b/src/clauses/where-comparators.ts index 5b5a8b5a..cbe8f29b 100644 --- a/src/clauses/where-comparators.ts +++ b/src/clauses/where-comparators.ts @@ -230,9 +230,13 @@ export function inArray(value: any[], variable?: boolean) { * to true because it will prepend `'(?i)'` which will make your regexp * malformed. * - * The regexp syntax is inherited from the - * [java regexp syntax]{@link - * https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html}. + * For convenience you can also pass a Javascript RegExp object into this + * comparator, which will then be converted into a string before it is + * passed to cypher. *However*, beware that the cypher regexp syntax is + * inherited from [java]{@link + * https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html}, + * and may have slight differences to the Javascript syntax. For example, + * Javascript RegExp flags will not be preserved when sent to cypher. * * If you want to compare against a Neo4j variable you can set `variable` to * true and the value will be inserted literally into the query. @@ -241,7 +245,10 @@ export function inArray(value: any[], variable?: boolean) { * query.where({ name: regexp('s.*e') }) * // WHERE name =~ 's.*e' * - * query.where({ name: regexp('clientPattern', true) }) + * query.where({ name: regexp('s.*e', true) }) + * // WHERE name =~ '(?i)s.*e' + * + * query.where({ name: regexp('clientPattern', false, true) }) * // WHERE name =~ clientPattern * ``` * @param exp @@ -249,8 +256,13 @@ export function inArray(value: any[], variable?: boolean) { * @param {boolean} variable * @returns {Comparator} */ -export function regexp(exp: string, insensitive?: boolean, variable?: boolean) { - return compare('=~', insensitive ? '(?i)' + exp : exp, variable); +export function regexp(exp: string | RegExp, insensitive?: boolean, variable?: boolean) { + let stringExp = exp; + if (exp instanceof RegExp) { + // Convert regular expression to string and strip slashes and trailing flags + stringExp = exp.toString().match(/\/(.*)\/[a-z]*/)[1]; + } + return compare('=~', insensitive ? '(?i)' + stringExp : stringExp, variable); } /** diff --git a/src/clauses/where-utils.ts b/src/clauses/where-utils.ts index a406f06d..99c2ab29 100644 --- a/src/clauses/where-utils.ts +++ b/src/clauses/where-utils.ts @@ -9,10 +9,11 @@ import { last, keys, isFunction, + isRegExp, } from 'lodash'; import { ParameterBag } from '../parameter-bag'; import { WhereOp } from './where-operators'; -import { Comparator } from './where-comparators'; +import { Comparator, regexp } from './where-comparators'; export type Condition = any | Comparator; export type Conditions = Dictionary>; @@ -57,6 +58,9 @@ export function stringCons( if (conditions instanceof WhereOp) { return conditions.evaluate(params, precedence, name); } + if (isRegExp(conditions)) { + return stringifyCondition(params, regexp(conditions), name); + } return stringifyCondition(params, conditions, name); } diff --git a/src/clauses/where.spec.ts b/src/clauses/where.spec.ts index 5bd62f5c..91664318 100644 --- a/src/clauses/where.spec.ts +++ b/src/clauses/where.spec.ts @@ -17,5 +17,21 @@ describe('Where', () => { upperAge: 65, }); }); + + it('should compile with a regular expression', () => { + const query = new Where({ name: /[A-Z].*son/ }); + expect(query.build()).to.equal('WHERE name =~ $name'); + expect(query.getParams()).to.deep.equal({ + name: '[A-Z].*son', + }); + }); + + it('should compile with a regular expression with flags', () => { + const query = new Where({ name: /.*son/i }); + expect(query.build()).to.equal('WHERE name =~ $name'); + expect(query.getParams()).to.deep.equal({ + name: '.*son', + }); + }); }); });