@@ -5,6 +5,7 @@ var SchemaController = require('./Controllers/SchemaController');
55var Parse = require ( 'parse/node' ) . Parse ;
66const triggers = require ( './triggers' ) ;
77
8+ const AlwaysSelectedKeys = [ 'objectId' , 'createdAt' , 'updatedAt' ] ;
89// restOptions can include:
910// skip
1011// limit
@@ -52,15 +53,36 @@ function RestQuery(config, auth, className, restWhere = {}, restOptions = {}, cl
5253 // this.include = [['foo'], ['foo', 'baz'], ['foo', 'bar']]
5354 this . include = [ ] ;
5455
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+
5579 for ( var option in restOptions ) {
5680 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 ) ) ;
6384 break ;
85+ }
6486 case 'count' :
6587 this . doCount = true ;
6688 break ;
@@ -80,22 +102,26 @@ function RestQuery(config, auth, className, restWhere = {}, restOptions = {}, cl
80102 }
81103 this . findOptions . sort = sortMap ;
82104 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 ) => {
96119 return s . split ( '.' ) ;
120+ } ) . sort ( ( a , b ) => {
121+ return a . length - b . length ; // Sort by number of components
97122 } ) ;
98123 break ;
124+ }
99125 case 'redirectClassNameForKey' :
100126 this . redirectKey = restOptions . redirectClassNameForKey ;
101127 this . redirectClassName = null ;
@@ -421,7 +447,7 @@ RestQuery.prototype.runFind = function(options = {}) {
421447 }
422448 let findOptions = Object . assign ( { } , this . findOptions ) ;
423449 if ( this . keys ) {
424- findOptions . keys = Array . from ( this . keys ) . map ( ( key ) => {
450+ findOptions . keys = this . keys . map ( ( key ) => {
425451 return key . split ( '.' ) [ 0 ] ;
426452 } ) ;
427453 }
0 commit comments