1
1
var quote = function ( data ) {
2
- if ( typeof data == 'string' )
3
- return "'" + stringEscape ( data ) + "'" ;
4
- return "'" + JSON . stringify ( data ) + "' ::jsonb"
2
+ if ( typeof data == 'string' )
3
+ return '\'' + stringEscape ( data ) + '\''
4
+ return '\'' + JSON . stringify ( data ) + '\' ::jsonb'
5
5
}
6
6
7
7
var stringEscape = function ( str ) {
8
- return str . replace ( / ' / g, "''" )
8
+ return str . replace ( / ' / g, '\'\'' )
9
9
}
10
10
11
11
var pathToText = function ( path , isString ) {
12
- var text = stringEscape ( path [ 0 ] ) ;
13
- for ( var i = 1 ; i < path . length ; i ++ ) {
14
- text += ( i == path . length - 1 && isString ? '->>' : '->' ) ;
15
- if ( / \d + / . test ( path [ i ] ) )
16
- text += path [ i ] ; //don't wrap numbers in quotes
17
- else
18
- text += '\'' + stringEscape ( path [ i ] ) + '\'' ;
19
- }
20
- return text ;
12
+ var text = stringEscape ( path [ 0 ] )
13
+ for ( var i = 1 ; i < path . length ; i ++ ) {
14
+ text += ( i == path . length - 1 && isString ? '->>' : '->' )
15
+ if ( / \d + / . test ( path [ i ] ) )
16
+ text += path [ i ] //don't wrap numbers in quotes
17
+ else
18
+ text += '\'' + stringEscape ( path [ i ] ) + '\''
19
+ }
20
+ return text
21
21
}
22
22
var convertDotNotation = function ( path , pathDotNotation ) {
23
- return pathToText ( [ path ] . concat ( pathDotNotation . split ( '.' ) ) , true ) ;
23
+ return pathToText ( [ path ] . concat ( pathDotNotation . split ( '.' ) ) , true )
24
24
}
25
25
26
26
// These are the simple operators.
27
27
var ops = {
28
- $eq : '=' ,
29
- $gt : '>' ,
30
- $gte : '>=' ,
31
- $lt : '<' ,
32
- $lte : '<=' ,
33
- $ne : '!=' ,
28
+ $eq : '=' ,
29
+ $gt : '>' ,
30
+ $gte : '>=' ,
31
+ $lt : '<' ,
32
+ $lte : '<=' ,
33
+ $ne : '!=' ,
34
34
}
35
35
36
36
var otherOps = {
37
- $in : true , $nin : true , $not : true , $or : true , $and : true , $elemMatch : true , $regex : true
37
+ $in : true , $nin : true , $not : true , $or : true , $and : true , $elemMatch : true , $regex : true , $type : true , $size : true , $exists : true , $mod : true
38
38
}
39
39
40
- var convert = function ( path , query ) {
41
- if ( typeof query == 'object' && ! Array . isArray ( query ) ) {
42
- var specialKeys = Object . keys ( query )
43
- if ( path . length > 1 ) {
44
- specialKeys = specialKeys . filter ( function ( key ) {
45
- return key in ops || key in otherOps ;
46
- } )
47
- }
48
- if ( specialKeys . length > 0 ) {
49
- var conditions = specialKeys . map ( function ( key ) {
50
- var value = query [ key ]
51
-
52
- if ( key == '$not' ) {
53
- return '(NOT ' + convert ( path , query [ key ] ) + ')' ;
54
- } else if ( key == '$or' || key == '$and' ) {
55
- if ( query [ key ] . length == 0 ) {
56
- return key == '$or' ? 'FALSE' : 'TRUE'
57
- } else {
58
- return '(' + query [ key ] . map ( function ( subquery ) { return convert ( path , subquery ) } )
59
- . join ( key == '$or' ? ' OR ' : ' AND ' ) + ')' ;
60
- }
61
- } else if ( key == '$regex' ) {
62
- var op = '~' ;
63
- if ( query [ '$options' ] && query [ '$options' ] . includes ( 'i' ) ) {
64
- op += '*' ;
65
- }
66
- return pathToText ( path , true ) + ' ' + op + ' \'' + stringEscape ( value ) + '\'' ;
67
- } else if ( key == '$elemMatch' ) {
68
- return pathToText ( path , false ) + ' @> \'' + stringEscape ( JSON . stringify ( query [ key ] ) ) + '\'::jsonb' ;
69
- } else if ( key == '$in' || key == '$nin' ) {
70
- return pathToText ( path , typeof query [ key ] [ 0 ] == 'string' ) + ( key == '$nin' ? ' NOT' : '' ) + ' IN (' + query [ key ] . map ( quote ) . join ( ', ' ) + ')' ;
71
- } else if ( Object . keys ( ops ) . indexOf ( key ) !== - 1 ) {
72
- var text = pathToText ( path , typeof query [ key ] == 'string' )
73
- return text + ops [ key ] + quote ( query [ key ] )
74
- } else {
75
- return convert ( path . concat ( key . split ( '.' ) ) , query [ key ] ) ;
76
- }
40
+ function convertOp ( path , op , value , parent , arrayPaths ) {
41
+ if ( arrayPaths ) {
42
+ for ( var arrPath of arrayPaths ) {
43
+ if ( op . startsWith ( arrPath ) ) {
44
+ var subPath = op . split ( '.' )
45
+ var innerPath = [ 'value' , subPath . pop ( ) ]
46
+ var innerText = pathToText ( innerPath , typeof value === 'string' )
47
+ path = path . concat ( subPath )
48
+ var text = pathToText ( path , false )
49
+ return 'EXISTS (SELECT * FROM jsonb_array_elements(' + text + ') WHERE ' + innerText + '=' + quote ( value ) + ')'
50
+ }
51
+ }
52
+ }
53
+ switch ( op ) {
54
+ case '$not' :
55
+ return '(NOT ' + convert ( path , value ) + ')'
56
+ case '$nor' :
57
+ var notted = value . map ( ( e ) => ( { $not : e } ) ) ;
58
+ return convertOp ( path , '$and' , notted , value , arrayPaths ) ;
59
+ case '$or' :
60
+ case '$and' :
61
+ if ( ! Array . isArray ( value ) ) {
62
+ throw new Error ( '$and or $or requires an array.' )
63
+ }
64
+ if ( value . length == 0 ) {
65
+ return ( op === '$or' ? 'FALSE' : 'TRUE' )
66
+ } else {
67
+ return '(' + value . map ( ( subquery ) => convert ( path , subquery ) ) . join ( op === '$or' ? ' OR ' : ' AND ' ) + ')'
68
+ }
69
+ case '$elemMatch' :
70
+ return pathToText ( path , false ) + ' @> \'' + stringEscape ( JSON . stringify ( value ) ) + '\'::jsonb'
71
+ case '$in' :
72
+ case '$nin' :
73
+ return pathToText ( path , typeof value [ 0 ] == 'string' ) + ( op == '$nin' ? ' NOT' : '' ) + ' IN (' + value . map ( quote ) . join ( ', ' ) + ')'
74
+ case '$regex' :
75
+ var op = '~'
76
+ if ( parent [ '$options' ] && parent [ '$options' ] . includes ( 'i' ) ) {
77
+ op += '*'
78
+ }
79
+ return pathToText ( path , true ) + ' ' + op + ' \'' + stringEscape ( value ) + '\''
80
+ case '$eq' :
81
+ case '$gt' :
82
+ case '$gte' :
83
+ case '$lt' :
84
+ case '$lte' :
85
+ case '$ne' :
86
+ var text = pathToText ( path , typeof value == 'string' )
87
+ return text + ops [ op ] + quote ( value )
88
+ case '$type' :
89
+ var text = pathToText ( path , false )
90
+ return 'jsonb_typeof(' + text + ')=' + quote ( value )
91
+ case '$size' :
92
+ var text = pathToText ( path , false )
93
+ return 'jsonb_array_length(' + text + ')=' + value
94
+ case '$exists' :
95
+ const key = path . pop ( ) ;
96
+ var text = pathToText ( path , false )
97
+ return text + ' ? ' + quote ( key )
98
+ case '$mod' :
99
+ var text = pathToText ( path , true )
100
+ if ( typeof value [ 0 ] != 'number' || typeof value [ 1 ] != 'number' ) {
101
+ throw new Error ( '$mod requires numeric inputs' )
102
+ }
103
+ return 'cast(' + text + ' AS numeric) % ' + value [ 0 ] + '=' + value [ 1 ] ;
104
+ default :
105
+ return convert ( path . concat ( op . split ( '.' ) ) , value )
106
+ }
107
+ }
77
108
78
- } ) . join ( ' and ' ) ;
79
- if ( specialKeys . length == 1 )
80
- return conditions ;
81
- else
82
- return '(' + conditions + ')'
83
- } else {
84
- if ( path . length == 1 ) {
85
- return 'TRUE' ;
86
- }
87
- var text = pathToText ( path , typeof query == 'string' )
88
- return text + '=' + quote ( query ) ;
89
- }
90
- } else {
91
- var text = pathToText ( path , typeof query == 'string' )
92
- return text + '=' + quote ( query ) ;
93
- }
109
+ var convert = function ( path , query , arrayPaths ) {
110
+ if ( typeof query === 'string' || typeof query === 'boolean' || typeof query == 'number' || Array . isArray ( query ) ) {
111
+ var text = pathToText ( path , typeof query == 'string' )
112
+ return text + '=' + quote ( query )
113
+ }
114
+ if ( typeof query == 'object' ) {
115
+ // Check for an empty object
116
+ if ( Object . keys ( query ) . length === 0 ) {
117
+ return 'TRUE'
118
+ }
119
+ var specialKeys = Object . keys ( query ) . filter ( function ( key ) {
120
+ return ( path . length === 1 ) || key in ops || key in otherOps
121
+ } )
122
+ switch ( specialKeys . length ) {
123
+ case 0 :
124
+ var text = pathToText ( path , typeof query == 'string' )
125
+ return text + '=' + quote ( query )
126
+ case 1 :
127
+ const key = specialKeys [ 0 ] ;
128
+ return convertOp ( path , key , query [ key ] , query , arrayPaths ) ;
129
+ default :
130
+ return '(' + specialKeys . map ( function ( key ) {
131
+ return convertOp ( path , key , query [ key ] , query , arrayPaths ) ;
132
+ } ) . join ( ' and ' ) + ')'
133
+ }
134
+ }
94
135
}
95
136
96
- module . exports = function ( fieldName , query ) {
97
- return convert ( [ fieldName ] , query ) ;
98
- } ;
137
+ module . exports = function ( fieldName , query , arrays ) {
138
+ return convert ( [ fieldName ] , query , arrays || [ ] )
139
+ }
99
140
module . exports . convertDotNotation = convertDotNotation
0 commit comments