@@ -5,6 +5,7 @@ var SchemaController = require('./Controllers/SchemaController');
5
5
var Parse = require ( 'parse/node' ) . Parse ;
6
6
const triggers = require ( './triggers' ) ;
7
7
8
+ const AlwaysSelectedKeys = [ 'objectId' , 'createdAt' , 'updatedAt' ] ;
8
9
// restOptions can include:
9
10
// skip
10
11
// limit
@@ -52,15 +53,36 @@ function RestQuery(config, auth, className, restWhere = {}, restOptions = {}, cl
52
53
// this.include = [['foo'], ['foo', 'baz'], ['foo', 'bar']]
53
54
this . include = [ ] ;
54
55
56
+ // If we have keys, we probably want to force some includes (n-1 level)
57
+ // See issue: https://github.com/ParsePlatform/parse-server/issues/3185
58
+ if ( restOptions . hasOwnProperty ( 'keys' ) ) {
59
+ const keysForInclude = restOptions . keys . split ( ',' ) . filter ( ( key ) => {
60
+ // At least 2 components
61
+ return key . split ( "." ) . length > 1 ;
62
+ } ) . map ( ( key ) => {
63
+ // Slice the last component (a.b.c -> a.b)
64
+ // Otherwise we'll include one level too much.
65
+ return key . slice ( 0 , key . lastIndexOf ( "." ) ) ;
66
+ } ) . join ( ',' ) ;
67
+
68
+ // Concat the possibly present include string with the one from the keys
69
+ // Dedup / sorting is handle in 'include' case.
70
+ if ( keysForInclude . length > 0 ) {
71
+ if ( ! restOptions . include || restOptions . include . length == 0 ) {
72
+ restOptions . include = keysForInclude ;
73
+ } else {
74
+ restOptions . include += "," + keysForInclude ;
75
+ }
76
+ }
77
+ }
78
+
55
79
for ( var option in restOptions ) {
56
80
switch ( option ) {
57
- case 'keys' :
58
- this . keys = new Set ( restOptions . keys . split ( ',' ) ) ;
59
- // Add the default
60
- this . keys . add ( 'objectId' ) ;
61
- this . keys . add ( 'createdAt' ) ;
62
- this . keys . add ( 'updatedAt' ) ;
81
+ case 'keys' : {
82
+ const keys = restOptions . keys . split ( ',' ) . concat ( AlwaysSelectedKeys ) ;
83
+ this . keys = Array . from ( new Set ( keys ) ) ;
63
84
break ;
85
+ }
64
86
case 'count' :
65
87
this . doCount = true ;
66
88
break ;
@@ -80,22 +102,26 @@ function RestQuery(config, auth, className, restWhere = {}, restOptions = {}, cl
80
102
}
81
103
this . findOptions . sort = sortMap ;
82
104
break ;
83
- case 'include' :
84
- var paths = restOptions . include . split ( ',' ) ;
85
- var pathSet = { } ;
86
- for ( var path of paths ) {
87
- // Add all prefixes with a .-split to pathSet
88
- var parts = path . split ( '.' ) ;
89
- for ( var len = 1 ; len <= parts . length ; len ++ ) {
90
- pathSet [ parts . slice ( 0 , len ) . join ( '.' ) ] = true ;
91
- }
92
- }
93
- this . include = Object . keys ( pathSet ) . sort ( ( a , b ) => {
94
- return a . length - b . length ;
95
- } ) . map ( ( s ) => {
105
+ case 'include' : {
106
+ const paths = restOptions . include . split ( ',' ) ;
107
+ // Load the existing includes (from keys)
108
+ const pathSet = paths . reduce ( ( memo , path ) => {
109
+ // Split each paths on . (a.b.c -> [a,b,c])
110
+ // reduce to create all paths
111
+ // ([a,b,c] -> {a: true, 'a.b': true, 'a.b.c': true})
112
+ return path . split ( '.' ) . reduce ( ( memo , path , index , parts ) => {
113
+ memo [ parts . slice ( 0 , index + 1 ) . join ( '.' ) ] = true ;
114
+ return memo ;
115
+ } , memo ) ;
116
+ } , { } ) ;
117
+
118
+ this . include = Object . keys ( pathSet ) . map ( ( s ) => {
96
119
return s . split ( '.' ) ;
120
+ } ) . sort ( ( a , b ) => {
121
+ return a . length - b . length ; // Sort by number of components
97
122
} ) ;
98
123
break ;
124
+ }
99
125
case 'redirectClassNameForKey' :
100
126
this . redirectKey = restOptions . redirectClassNameForKey ;
101
127
this . redirectClassName = null ;
@@ -421,7 +447,7 @@ RestQuery.prototype.runFind = function(options = {}) {
421
447
}
422
448
let findOptions = Object . assign ( { } , this . findOptions ) ;
423
449
if ( this . keys ) {
424
- findOptions . keys = Array . from ( this . keys ) . map ( ( key ) => {
450
+ findOptions . keys = this . keys . map ( ( key ) => {
425
451
return key . split ( '.' ) [ 0 ] ;
426
452
} ) ;
427
453
}
0 commit comments