Skip to content

Commit 0418cf2

Browse files
committed
Add support for registering custom query operators. Closes #60;
1 parent cc46463 commit 0418cf2

File tree

2 files changed

+134
-98
lines changed

2 files changed

+134
-98
lines changed

src/index.js

Lines changed: 110 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ class DSSqlAdapter {
156156
constructor (options) {
157157
this.defaults = {}
158158
options = options || {}
159+
160+
if (options.queryOperators) {
161+
this.queryOperators = options.queryOperators;
162+
delete options.queryOperators;
163+
}
164+
159165
if (options.__knex__) {
160166
this.query = options
161167
} else {
@@ -347,107 +353,113 @@ class DSSqlAdapter {
347353
}
348354

349355
forOwn(criteria, (v, op) => {
350-
if (op === '==' || op === '===') {
351-
if (v === null) {
352-
query = query.whereNull(field)
353-
} else {
354-
query = query.where(field, v)
355-
}
356-
} else if (op === '!=' || op === '!==') {
357-
if (v === null) {
358-
query = query.whereNotNull(field)
359-
} else {
360-
query = query.where(field, '!=', v)
361-
}
362-
} else if (op === '>') {
363-
query = query.where(field, '>', v)
364-
} else if (op === '>=') {
365-
query = query.where(field, '>=', v)
366-
} else if (op === '<') {
367-
query = query.where(field, '<', v)
368-
} else if (op === '<=') {
369-
query = query.where(field, '<=', v)
370-
// } else if (op === 'isectEmpty') {
371-
// subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
372-
// } else if (op === 'isectNotEmpty') {
373-
// subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
374-
} else if (op === 'in') {
375-
query = query.where(field, 'in', v)
376-
} else if (op === 'notIn') {
377-
query = query.whereNotIn(field, v)
378-
} else if (op === 'near') {
379-
const milesRegex = /(\d+(\.\d+)?)\s*(m|M)iles$/
380-
const kilometersRegex = /(\d+(\.\d+)?)\s*(k|K)$/
381-
382-
let radius
383-
let unitsPerDegree
384-
if (typeof v.radius === 'number' || milesRegex.test(v.radius)) {
385-
radius = typeof v.radius === 'number' ? v.radius : v.radius.match(milesRegex)[1]
386-
unitsPerDegree = 69.0 // miles per degree
387-
} else if (kilometersRegex.test(v.radius)) {
388-
radius = v.radius.match(kilometersRegex)[1]
389-
unitsPerDegree = 111.045 // kilometers per degree;
390-
} else {
391-
throw new Error('Unknown radius distance units')
392-
}
356+
if (op in (this.queryOperators || {})) {
357+
// Custom or overridden operator
358+
query = this.queryOperators[op](query, field, v)
359+
} else {
360+
// Builtin operators
361+
if (op === '==' || op === '===') {
362+
if (v === null) {
363+
query = query.whereNull(field)
364+
} else {
365+
query.where(field, v)
366+
}
367+
} else if (op === '!=' || op === '!==') {
368+
if (v === null) {
369+
query = query.whereNotNull(field)
370+
} else {
371+
query = query.where(field, '!=', v)
372+
}
373+
} else if (op === '>') {
374+
query = query.where(field, '>', v)
375+
} else if (op === '>=') {
376+
query = query.where(field, '>=', v)
377+
} else if (op === '<') {
378+
query = query.where(field, '<', v)
379+
} else if (op === '<=') {
380+
query = query.where(field, '<=', v)
381+
// } else if (op === 'isectEmpty') {
382+
// subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
383+
// } else if (op === 'isectNotEmpty') {
384+
// subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
385+
} else if (op === 'in') {
386+
query = query.where(field, 'in', v)
387+
} else if (op === 'notIn') {
388+
query = query.whereNotIn(field, v)
389+
} else if (op === 'near') {
390+
const milesRegex = /(\d+(\.\d+)?)\s*(m|M)iles$/
391+
const kilometersRegex = /(\d+(\.\d+)?)\s*(k|K)$/
392+
393+
let radius
394+
let unitsPerDegree
395+
if (typeof v.radius === 'number' || milesRegex.test(v.radius)) {
396+
radius = typeof v.radius === 'number' ? v.radius : v.radius.match(milesRegex)[1]
397+
unitsPerDegree = 69.0 // miles per degree
398+
} else if (kilometersRegex.test(v.radius)) {
399+
radius = v.radius.match(kilometersRegex)[1]
400+
unitsPerDegree = 111.045 // kilometers per degree;
401+
} else {
402+
throw new Error('Unknown radius distance units')
403+
}
393404

394-
let [latitudeColumn, longitudeColumn] = field.split(',').map(c => c.trim())
395-
let [latitude, longitude] = v.center
396-
397-
// Uses indexes on `latitudeColumn` / `longitudeColumn` if available
398-
query = query
399-
.whereBetween(latitudeColumn, [
400-
latitude - (radius / unitsPerDegree),
401-
latitude + (radius / unitsPerDegree)
402-
])
403-
.whereBetween(longitudeColumn, [
404-
longitude - (radius / (unitsPerDegree * Math.cos(latitude * (Math.PI / 180)))),
405-
longitude + (radius / (unitsPerDegree * Math.cos(latitude * (Math.PI / 180))))
406-
])
407-
408-
if (v.calculateDistance) {
409-
let distanceColumn = (typeof v.calculateDistance === 'string') ? v.calculateDistance : 'distance'
410-
query = query.select(knex.raw(`
411-
${unitsPerDegree} * DEGREES(ACOS(
412-
COS(RADIANS(?)) * COS(RADIANS(${latitudeColumn})) *
413-
COS(RADIANS(${longitudeColumn}) - RADIANS(?)) +
414-
SIN(RADIANS(?)) * SIN(RADIANS(${latitudeColumn}))
415-
)) AS ${distanceColumn}`, [latitude, longitude, latitude]))
416-
}
417-
} else if (op === 'like') {
418-
query = query.where(field, 'like', v)
419-
} else if (op === '|like') {
420-
query = query.orWhere(field, 'like', v)
421-
} else if (op === '|==' || op === '|===') {
422-
if (v === null) {
423-
query = query.orWhereNull(field)
424-
} else {
425-
query = query.orWhere(field, v)
426-
}
427-
} else if (op === '|!=' || op === '|!==') {
428-
if (v === null) {
429-
query = query.orWhereNotNull(field)
405+
let [latitudeColumn, longitudeColumn] = field.split(',').map(c => c.trim())
406+
let [latitude, longitude] = v.center
407+
408+
// Uses indexes on `latitudeColumn` / `longitudeColumn` if available
409+
query = query
410+
.whereBetween(latitudeColumn, [
411+
latitude - (radius / unitsPerDegree),
412+
latitude + (radius / unitsPerDegree)
413+
])
414+
.whereBetween(longitudeColumn, [
415+
longitude - (radius / (unitsPerDegree * Math.cos(latitude * (Math.PI / 180)))),
416+
longitude + (radius / (unitsPerDegree * Math.cos(latitude * (Math.PI / 180))))
417+
])
418+
419+
if (v.calculateDistance) {
420+
let distanceColumn = (typeof v.calculateDistance === 'string') ? v.calculateDistance : 'distance'
421+
query = query.select(knex.raw(`
422+
${unitsPerDegree} * DEGREES(ACOS(
423+
COS(RADIANS(?)) * COS(RADIANS(${latitudeColumn})) *
424+
COS(RADIANS(${longitudeColumn}) - RADIANS(?)) +
425+
SIN(RADIANS(?)) * SIN(RADIANS(${latitudeColumn}))
426+
)) AS ${distanceColumn}`, [latitude, longitude, latitude]))
427+
}
428+
} else if (op === 'like') {
429+
query = query.where(field, 'like', v)
430+
} else if (op === '|like') {
431+
query = query.orWhere(field, 'like', v)
432+
} else if (op === '|==' || op === '|===') {
433+
if (v === null) {
434+
query = query.orWhereNull(field)
435+
} else {
436+
query = query.orWhere(field, v)
437+
}
438+
} else if (op === '|!=' || op === '|!==') {
439+
if (v === null) {
440+
query = query.orWhereNotNull(field)
441+
} else {
442+
query = query.orWhere(field, '!=', v)
443+
}
444+
} else if (op === '|>') {
445+
query = query.orWhere(field, '>', v)
446+
} else if (op === '|>=') {
447+
query = query.orWhere(field, '>=', v)
448+
} else if (op === '|<') {
449+
query = query.orWhere(field, '<', v)
450+
} else if (op === '|<=') {
451+
query = query.orWhere(field, '<=', v)
452+
// } else if (op === '|isectEmpty') {
453+
// subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
454+
// } else if (op === '|isectNotEmpty') {
455+
// subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
456+
} else if (op === '|in') {
457+
query = query.orWhere(field, 'in', v)
458+
} else if (op === '|notIn') {
459+
query = query.orWhereNotIn(field, v)
430460
} else {
431-
query = query.orWhere(field, '!=', v)
461+
throw new Error('Operator not found')
432462
}
433-
} else if (op === '|>') {
434-
query = query.orWhere(field, '>', v)
435-
} else if (op === '|>=') {
436-
query = query.orWhere(field, '>=', v)
437-
} else if (op === '|<') {
438-
query = query.orWhere(field, '<', v)
439-
} else if (op === '|<=') {
440-
query = query.orWhere(field, '<=', v)
441-
// } else if (op === '|isectEmpty') {
442-
// subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
443-
// } else if (op === '|isectNotEmpty') {
444-
// subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
445-
} else if (op === '|in') {
446-
query = query.orWhere(field, 'in', v)
447-
} else if (op === '|notIn') {
448-
query = query.orWhereNotIn(field, v)
449-
} else {
450-
throw new Error('Operator not found')
451463
}
452464
})
453465
})

test/filterQuery.spec.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,28 @@ describe('DSSqlAdapter#filterQuery', function () {
9898

9999
assert.equal(filterQuery.toString(), expectedQuery.toString())
100100
});
101+
102+
describe('Custom/override query operators', function () {
103+
it('should use custom query operator if provided', function* () {
104+
adapter.queryOperators = { "equals": (query, field, value) => query.where(field, value) }
105+
var filterQuery = adapter.filterQuery(User, { name: { "equals": "Sean" }});
106+
var expectedQuery = adapter.query
107+
.from('user')
108+
.select('user.*')
109+
.where('name', 'Sean')
110+
111+
assert.equal(filterQuery.toString(), expectedQuery.toString())
112+
});
113+
114+
it('should override built-in operator with custom query operator', function* () {
115+
adapter.queryOperators = { "==": (query, field, value) => query.where(field, '!=', value) }
116+
var filterQuery = adapter.filterQuery(User, { name: { "==": "Sean" }});
117+
var expectedQuery = adapter.query
118+
.from('user')
119+
.select('user.*')
120+
.where('name', '!=', 'Sean')
121+
122+
assert.equal(filterQuery.toString(), expectedQuery.toString())
123+
});
124+
});
101125
});

0 commit comments

Comments
 (0)