Skip to content

Commit dd4455a

Browse files
committed
Refactor and fix lint
1 parent ccd8def commit dd4455a

7 files changed

+811
-109
lines changed

.eslintrc.js

+28-27
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
module.exports = {
2-
"env": {
3-
"node": true,
4-
"mocha": true
5-
},
6-
"extends": "eslint:recommended",
7-
"parserOptions": {
8-
"ecmaVersion": 2017,
9-
},
10-
"rules": {
11-
"indent": [
12-
"error",
13-
2
14-
],
15-
"linebreak-style": [
16-
"error",
17-
"unix"
18-
],
19-
"quotes": [
20-
"error",
21-
"single"
22-
],
23-
"semi": [
24-
"error",
25-
"never"
26-
]
27-
}
28-
};
2+
'env': {
3+
'node': true,
4+
'mocha': true
5+
},
6+
'extends': 'eslint:recommended',
7+
'parserOptions': {
8+
'ecmaVersion': 2017,
9+
},
10+
'rules': {
11+
'indent': [
12+
'error',
13+
2,
14+
{ 'SwitchCase': 1 }
15+
],
16+
'linebreak-style': [
17+
'error',
18+
'unix'
19+
],
20+
'quotes': [
21+
'error',
22+
'single'
23+
],
24+
'semi': [
25+
'error',
26+
'never'
27+
]
28+
}
29+
}

index.js

+96-71
Original file line numberDiff line numberDiff line change
@@ -15,62 +15,81 @@ var otherOps = {
1515
$all: true, $in: true, $nin: true, $not: true, $or: true, $and: true, $elemMatch: true, $regex: true, $type: true, $size: true, $exists: true, $mod: true, $text: true
1616
}
1717

18-
function convertOp(path, op, value, parent, arrayPaths) {
19-
if (arrayPaths) {
20-
for (var arrPath of arrayPaths) {
21-
if (op.startsWith(arrPath)) {
22-
const subPath = op.split('.')
23-
const innerPath = subPath.length > 1 ? ['value', subPath.pop()] : ['value']
24-
const singleElementQuery = convertOp(path, op, value, parent, [])
25-
path = path.concat(subPath)
26-
const text = util.pathToText(path, false)
27-
const safeArray = `jsonb_typeof(${text})='array' AND`
28-
let arrayQuery = ''
29-
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
30-
if (typeof value['$size'] !== 'undefined') {
31-
// size does not support array element based matching
32-
} else if (value['$elemMatch']) {
33-
const sub = convert(innerPath, value['$elemMatch'], [], false)
34-
arrayQuery = `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
35-
return arrayQuery
36-
} else if (value['$in']) {
37-
const sub = convert(innerPath, value, [], true)
38-
arrayQuery = `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
39-
} else if (value['$all']) {
40-
const cleanedValue = value['$all'].filter((v) => (v !== null && typeof v !== 'undefined'))
41-
arrayQuery = '(' + cleanedValue.map(function (subquery) {
42-
const sub = convert(innerPath, subquery, [], false)
43-
return `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
44-
}).join(' AND ') + ')'
45-
} else {
46-
const params = value
47-
arrayQuery = '(' + Object.keys(params).map(function (subKey) {
48-
const sub = convert(innerPath, { [subKey]: params[subKey] }, [], true)
49-
return `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
50-
}).join(' AND ') + ')'
51-
}
52-
} else {
53-
const sub = convert(innerPath, value, [], true)
54-
arrayQuery = `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
55-
}
56-
if (!arrayQuery || arrayQuery === '()') {
57-
return singleElementQuery
58-
}
59-
return `(${singleElementQuery} OR ${arrayQuery})`
60-
}
18+
function shouldQueryAsArray(op, arrayPaths) {
19+
if (arrayPaths === true) {
20+
// always assume array path if true is passed
21+
return true
22+
}
23+
if (!arrayPaths || !Array.isArray(arrayPaths)) {
24+
return false
25+
}
26+
return arrayPaths.some(op => op.startsWith(arrayPaths))
27+
}
28+
29+
function createElementOrArrayQuery(path, op, value, parent) {
30+
const subPath = op.split('.')
31+
const innerPath = subPath.length > 1 ? ['value', subPath.pop()] : ['value']
32+
const singleElementQuery = convertOp(path, op, value, parent, [])
33+
path = path.concat(subPath)
34+
const text = util.pathToText(path, false)
35+
const safeArray = `jsonb_typeof(${text})='array' AND`
36+
let arrayQuery = ''
37+
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
38+
if (typeof value['$size'] !== 'undefined') {
39+
// size does not support array element based matching
40+
} else if (value['$elemMatch']) {
41+
const sub = convert(innerPath, value['$elemMatch'], [], false)
42+
arrayQuery = `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
43+
return arrayQuery
44+
} else if (value['$in']) {
45+
const sub = convert(innerPath, value, [], true)
46+
arrayQuery = `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
47+
} else if (value['$all']) {
48+
const cleanedValue = value['$all'].filter((v) => (v !== null && typeof v !== 'undefined'))
49+
arrayQuery = '(' + cleanedValue.map(function (subquery) {
50+
const sub = convert(innerPath, subquery, [], false)
51+
return `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
52+
}).join(' AND ') + ')'
53+
} else {
54+
const params = value
55+
arrayQuery = '(' + Object.keys(params).map(function (subKey) {
56+
const sub = convert(innerPath, { [subKey]: params[subKey] }, [], true)
57+
return `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
58+
}).join(' AND ') + ')'
6159
}
60+
} else {
61+
const sub = convert(innerPath, value, [], true)
62+
arrayQuery = `EXISTS (SELECT * FROM jsonb_array_elements(${text}) WHERE ${safeArray} ${sub})`
63+
}
64+
if (!arrayQuery || arrayQuery === '()') {
65+
return singleElementQuery
66+
}
67+
return `(${singleElementQuery} OR ${arrayQuery})`
68+
}
69+
70+
/**
71+
* @param path {string} a dotted path
72+
* @param op {string} sub path, especially the current operation to convert, e.g. $in
73+
* @param value {mixed}
74+
* @param parent {mixed} parent[path] = value
75+
* @param arrayPaths {Array} List of dotted paths that possibly need to be handled as arrays.
76+
*/
77+
function convertOp(path, op, value, parent, arrayPaths) {
78+
if (shouldQueryAsArray(op, arrayPaths)) {
79+
return createElementOrArrayQuery(path, op, value, parent)
6280
}
6381
switch(op) {
6482
case '$not':
6583
return '(NOT ' + convert(path, value) + ')'
66-
case '$nor':
84+
case '$nor': {
6785
for (const v of value) {
6886
if (typeof v !== 'object') {
6987
throw new Error('$or/$and/$nor entries need to be full objects')
7088
}
7189
}
72-
var notted = value.map((e) => ({ $not: e }))
90+
const notted = value.map((e) => ({ $not: e }))
7391
return convertOp(path, '$and', notted, value, arrayPaths)
92+
}
7493
case '$or':
7594
case '$and':
7695
if (!Array.isArray(value)) {
@@ -91,7 +110,7 @@ function convertOp(path, op, value, parent, arrayPaths) {
91110
return convert(path, value, arrayPaths)
92111
//return util.pathToText(path, false) + ' @> \'' + util.stringEscape(JSON.stringify(value)) + '\'::jsonb'
93112
case '$in':
94-
case '$nin':
113+
case '$nin': {
95114
if (value.length === 0) {
96115
return 'FALSE'
97116
}
@@ -104,18 +123,16 @@ function convertOp(path, op, value, parent, arrayPaths) {
104123
return (op === '$in' ? '(' + partial + ' OR IS NULL)' : '(' + partial + ' AND IS NOT NULL)' )
105124
}
106125
return partial
107-
case '$text':
108-
var op = '~'
109-
var op2 = ''
110-
if (!value['$caseSensitive']) {
111-
op += '*'
112-
}
113-
return util.pathToText(path, true) + ' ' + op + ' \'' + op2 + util.stringEscape(value['$search']) + '\''
114-
case '$regex':
115-
var op = '~'
126+
}
127+
case '$text': {
128+
const newOp = '~' + (!value['$caseSensitive'] ? '*' : '')
129+
return util.pathToText(path, true) + ' ' + newOp + ' \'' + util.stringEscape(value['$search']) + '\''
130+
}
131+
case '$regex': {
132+
var regexOp = '~'
116133
var op2 = ''
117134
if (parent['$options'] && parent['$options'].includes('i')) {
118-
op += '*'
135+
regexOp += '*'
119136
}
120137
if (!parent['$options'] || !parent['$options'].includes('s')) {
121138
// partial newline-sensitive matching
@@ -124,13 +141,14 @@ function convertOp(path, op, value, parent, arrayPaths) {
124141
if (value instanceof RegExp) {
125142
value = value.source
126143
}
127-
return util.pathToText(path, true) + ' ' + op + ' \'' + op2 + util.stringEscape(value) + '\''
144+
return util.pathToText(path, true) + ' ' + regexOp + ' \'' + op2 + util.stringEscape(value) + '\''
145+
}
128146
case '$gt':
129147
case '$gte':
130148
case '$lt':
131149
case '$lte':
132150
case '$ne':
133-
case '$eq':
151+
case '$eq': {
134152
const isSimpleComparision = (op === '$eq' || op === '$ne')
135153
const pathContainsArrayAccess = path.some((key) => /^\d+$/.test(key))
136154
if (isSimpleComparision && !pathContainsArrayAccess) {
@@ -142,31 +160,36 @@ function convertOp(path, op, value, parent, arrayPaths) {
142160
var text = util.pathToText(path, typeof value == 'string')
143161
return text + ops[op] + util.quote(value)
144162
}
145-
case '$type':
146-
var text = util.pathToText(path, false)
163+
}
164+
case '$type': {
165+
const text = util.pathToText(path, false)
147166
const type = util.getPostgresTypeName(value)
148167
return 'jsonb_typeof(' + text + ')=' + util.quote(type)
149-
case '$size':
168+
}
169+
case '$size': {
150170
if (typeof value !== 'number' || value < 0 || !Number.isInteger(value)) {
151171
throw new Error('$size only supports positive integer')
152172
}
153-
var text = util.pathToText(path, false)
173+
const text = util.pathToText(path, false)
154174
return 'jsonb_array_length(' + text + ')=' + value
155-
case '$exists':
175+
}
176+
case '$exists': {
156177
if (path.length > 1) {
157178
const key = path.pop()
158-
var text = util.pathToText(path, false)
179+
const text = util.pathToText(path, false)
159180
return (value ? '' : ' NOT ') + text + ' ? ' + util.quote(key)
160181
} else {
161-
var text = util.pathToText(path, false)
182+
const text = util.pathToText(path, false)
162183
return text + ' IS ' + (value ? 'NOT ' : '') + 'NULL'
163184
}
164-
case '$mod':
165-
var text = util.pathToText(path, true)
185+
}
186+
case '$mod': {
187+
const text = util.pathToText(path, true)
166188
if (typeof value[0] != 'number' || typeof value[1] != 'number') {
167189
throw new Error('$mod requires numeric inputs')
168190
}
169191
return 'cast(' + text + ' AS numeric) % ' + value[0] + '=' + value[1]
192+
}
170193
default:
171194
return convert(path.concat(op.split('.')), value)
172195
}
@@ -185,7 +208,7 @@ var convert = function (path, query, arrayPaths, forceExact=false) {
185208
return convertOp(path, '$eq', query, {}, arrayPaths)
186209
}
187210
if (query === null) {
188-
var text = util.pathToText(path, false)
211+
const text = util.pathToText(path, false)
189212
return '(' + text + ' IS NULL OR ' + text + ' = \'null\'::jsonb)'
190213
}
191214
if (query instanceof RegExp) {
@@ -201,12 +224,14 @@ var convert = function (path, query, arrayPaths, forceExact=false) {
201224
return (path.length === 1 && !forceExact) || key in ops || key in otherOps
202225
})
203226
switch (specialKeys.length) {
204-
case 0:
205-
var text = util.pathToText(path, typeof query == 'string')
227+
case 0: {
228+
const text = util.pathToText(path, typeof query == 'string')
206229
return text + '=' + util.quote(query)
207-
case 1:
230+
}
231+
case 1: {
208232
const key = specialKeys[0]
209233
return convertOp(path, key, query[key], query, arrayPaths)
234+
}
210235
default:
211236
return '(' + specialKeys.map(function (key) {
212237
return convertOp(path, key, query[key], query, arrayPaths)

0 commit comments

Comments
 (0)