@@ -11,24 +11,48 @@ var ops = {
11
11
}
12
12
13
13
var otherOps = {
14
- $in : true , $nin : true , $not : true , $or : true , $and : true , $elemMatch : true , $regex : true , $type : true , $size : true , $exists : true , $mod : true
14
+ $all : true , $ in : true , $nin : true , $not : true , $or : true , $and : true , $elemMatch : true , $regex : true , $type : true , $size : true , $exists : true , $mod : true
15
15
}
16
16
17
17
function convertOp ( path , op , value , parent , arrayPaths ) {
18
18
if ( arrayPaths ) {
19
19
for ( var arrPath of arrayPaths ) {
20
20
if ( op . startsWith ( arrPath ) ) {
21
- var subPath = op . split ( '.' )
22
- var innerPath = subPath . length > 1 ? [ 'value' , subPath . pop ( ) ] : [ 'value' ]
23
- var innerText = util . pathToText ( innerPath , typeof value === 'string' )
21
+ const subPath = op . split ( '.' )
22
+ const innerPath = subPath . length > 1 ? [ 'value' , subPath . pop ( ) ] : [ 'value' ]
23
+ const singleElementQuery = convertOp ( path , op , value , parent , [ ] )
24
24
path = path . concat ( subPath )
25
- var text = util . pathToText ( path , false )
26
- if ( value [ '$in' ] ) {
27
- const sub = convert ( innerPath , value )
28
- return 'EXISTS (SELECT * FROM jsonb_array_elements(' + text + ') WHERE ' + sub + ')'
25
+ const text = util . pathToText ( path , false )
26
+ const safeArray = "jsonb_typeof(data->'a')='array' AND" ;
27
+ let arrayQuery = '' ;
28
+ if ( typeof value === 'object' && ! Array . isArray ( value ) && value !== null ) {
29
+ if ( value [ '$elemMatch' ] ) {
30
+ const sub = convert ( innerPath , value [ '$elemMatch' ] , [ ] , false )
31
+ arrayQuery = `EXISTS (SELECT * FROM jsonb_array_elements(${ text } ) WHERE ${ safeArray } ${ sub } )`
32
+ } else if ( value [ '$in' ] ) {
33
+ const sub = convert ( innerPath , value , [ ] , true )
34
+ arrayQuery = `EXISTS (SELECT * FROM jsonb_array_elements(${ text } ) WHERE ${ safeArray } ${ sub } )`
35
+ } else if ( value [ '$all' ] ) {
36
+ const cleanedValue = value [ '$all' ] . filter ( ( v ) => ( v !== null && typeof v !== 'undefined' ) )
37
+ arrayQuery = '(' + cleanedValue . map ( function ( subquery ) {
38
+ const sub = convert ( innerPath , subquery , [ ] , false )
39
+ return `EXISTS (SELECT * FROM jsonb_array_elements(${ text } ) WHERE ${ safeArray } ${ sub } )`
40
+ } ) . join ( ' AND ' ) + ')'
41
+ } else {
42
+ const params = value
43
+ arrayQuery = '(' + Object . keys ( params ) . map ( function ( subKey ) {
44
+ const sub = convert ( innerPath , { [ subKey ] : params [ subKey ] } , [ ] , true )
45
+ return `EXISTS (SELECT * FROM jsonb_array_elements(${ text } ) WHERE ${ safeArray } ${ sub } )`
46
+ } ) . join ( ' AND ' ) + ')'
47
+ }
29
48
} else {
30
- return 'EXISTS (SELECT * FROM jsonb_array_elements(' + text + ') WHERE ' + innerText + '=' + util . quote ( value ) + ')'
49
+ const sub = convert ( innerPath , value , [ ] , true )
50
+ arrayQuery = `EXISTS (SELECT * FROM jsonb_array_elements(${ text } ) WHERE ${ safeArray } ${ sub } )`
51
+ }
52
+ if ( ! arrayQuery ) {
53
+ return singleElementQuery
31
54
}
55
+ return `(${ singleElementQuery } OR ${ arrayQuery } )`
32
56
}
33
57
}
34
58
}
@@ -53,8 +77,10 @@ function convertOp(path, op, value, parent, arrayPaths) {
53
77
}
54
78
return '(' + value . map ( ( subquery ) => convert ( path , subquery ) ) . join ( op === '$or' ? ' OR ' : ' AND ' ) + ')'
55
79
}
80
+ // TODO (make sure this handles multiple elements correctly)
56
81
case '$elemMatch' :
57
- return util . pathToText ( path , false ) + ' @> \'' + util . stringEscape ( JSON . stringify ( value ) ) + '\'::jsonb'
82
+ return convert ( path , value , arrayPaths )
83
+ //return util.pathToText(path, false) + ' @> \'' + util.stringEscape(JSON.stringify(value)) + '\'::jsonb'
58
84
case '$in' :
59
85
case '$nin' :
60
86
if ( value . length === 1 ) {
@@ -105,7 +131,15 @@ function convertOp(path, op, value, parent, arrayPaths) {
105
131
}
106
132
}
107
133
108
- var convert = function ( path , query , arrayPaths ) {
134
+ /**
135
+ * Convert a filter expression to the corresponding PostgreSQL text.
136
+ * @param path {Array} The current path
137
+ * @param query {Mixed} Any value
138
+ * @param arrayPaths {Array} List of dotted paths that possibly need to be handled as arrays.
139
+ * @param forceExact {Boolean} When true, an exact match will be required.
140
+ * @returns The corresponding PSQL expression
141
+ */
142
+ var convert = function ( path , query , arrayPaths , forceExact = false ) {
109
143
if ( typeof query === 'string' || typeof query === 'boolean' || typeof query == 'number' || Array . isArray ( query ) ) {
110
144
var text = util . pathToText ( path , typeof query == 'string' )
111
145
return text + '=' + util . quote ( query )
@@ -124,7 +158,7 @@ var convert = function (path, query, arrayPaths) {
124
158
return 'TRUE'
125
159
}
126
160
var specialKeys = Object . keys ( query ) . filter ( function ( key ) {
127
- return ( path . length === 1 ) || key in ops || key in otherOps
161
+ return ( path . length === 1 && ! forceExact ) || key in ops || key in otherOps
128
162
} )
129
163
switch ( specialKeys . length ) {
130
164
case 0 :
0 commit comments