From dfd566776909995bc89b0186af59134e642b3326 Mon Sep 17 00:00:00 2001 From: "Raschid J.F. Rafaelly" Date: Mon, 24 Jun 2019 03:04:35 -0500 Subject: [PATCH 1/2] Added restOption: excludeKeys --- spec/ParseQuery.spec.js | 74 +++++++++++++++++++++++++++++++++++- src/RestQuery.js | 29 +++++++++++++- src/Routers/ClassesRouter.js | 4 ++ 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 512fb82a13..0151b9ed91 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -3266,8 +3266,8 @@ describe('Parse.Query testing', () => { ); }); - it('select keys query', function(done) { - const obj = new TestObject({ foo: 'baz', bar: 1 }); + it('select/unselect keys query', function(done) { + const obj = new TestObject({ foo: 'baz', bar: 1, wooz: [1, 2, 3] }); obj .save() @@ -3288,11 +3288,17 @@ describe('Parse.Query testing', () => { undefined, "expected 'bar' field to be unset" ); + strictEqual( + result.get('wooz'), + undefined, + "expected 'wooz' field to be unset" + ); return result.fetch(); }) .then(function(result) { strictEqual(result.get('foo'), 'baz'); strictEqual(result.get('bar'), 1); + strictEqual(result.get('wooz')[0], 1); }) .then(function() { obj._clearServerData(); @@ -3313,6 +3319,11 @@ describe('Parse.Query testing', () => { undefined, "expected 'bar' field to be unset" ); + strictEqual( + result.get('wooz'), + undefined, + "expected 'wooz' field to be unset" + ); }) .then(function() { obj._clearServerData(); @@ -3325,6 +3336,11 @@ describe('Parse.Query testing', () => { ok(!result.dirty(), 'expected result not to be dirty'); strictEqual(result.get('foo'), 'baz'); strictEqual(result.get('bar'), 1); + strictEqual( + result.get('wooz'), + undefined, + "expected 'wooz' field to be unset" + ); }) .then(function() { obj._clearServerData(); @@ -3337,6 +3353,60 @@ describe('Parse.Query testing', () => { ok(!result.dirty(), 'expected result not to be dirty'); strictEqual(result.get('foo'), 'baz'); strictEqual(result.get('bar'), 1); + strictEqual( + result.get('wooz'), + undefined, + "expected 'wooz' field to be unset" + ); + }) + .then(function() { + obj._clearServerData(); + return request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + excludeKeys: 'bar,wooz', + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey, + 'Content-Type': 'application/json', + }, + }); + }) + .then(function(result) { + result = result.data.results[0]; + strictEqual(result.foo, 'baz'); + strictEqual(result.bar, undefined, "expected 'bar' field to be unset"); + strictEqual( + result.wooz, + undefined, + "expected 'wooz' field to be unset" + ); + }) + .then(function() { + obj._clearServerData(); + return request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + keys: 'foo', + excludeKeys: 'foo', + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey, + 'Content-Type': 'application/json', + }, + }); + }) + .then(function(result) { + result = result.data.results[0]; + strictEqual(result.foo, undefined, "expected 'foo' field to be unset"); + strictEqual(result.bar, undefined, "expected 'bar' field to be unset"); + strictEqual( + result.wooz, + undefined, + "expected 'wooz' field to be unset" + ); }) .then( function() { diff --git a/src/RestQuery.js b/src/RestQuery.js index 443668c374..3fb18f2d6c 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -13,6 +13,7 @@ const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL']; // count // include // keys +// excludeKeys // redirectClassNameForKey // readPreference // includeReadPreference @@ -102,6 +103,13 @@ function RestQuery( this.keys = Array.from(new Set(keys)); break; } + case 'excludeKeys': { + const exclude = restOptions.excludeKeys + .split(',') + .filter(k => AlwaysSelectedKeys.indexOf(k) < 0); + this.excludeKeys = Array.from(new Set(exclude)); + break; + } case 'count': this.doCount = true; break; @@ -184,6 +192,9 @@ RestQuery.prototype.execute = function(executeOptions) { .then(() => { return this.handleIncludeAll(); }) + .then(() => { + return this.handleExcludeKeys(); + }) .then(() => { return this.runFind(executeOptions); }) @@ -700,11 +711,27 @@ RestQuery.prototype.handleIncludeAll = function() { this.include = [...new Set([...this.include, ...includeFields])]; // if this.keys not set, then all keys are already included if (this.keys) { - this.keys = [...new Set([...this.keys, ...keyFields])]; + this.keys = [...new Set([...this.keys, ...keyFields])] + // Ignore excluded keys + .filter(k => !this.excludeKeys || this.excludeKeys.indexOf(k) < 0); } }); }; +// Upadates property `this.keys` to contain all keys but the ones unselected. +RestQuery.prototype.handleExcludeKeys = function() { + if (!this.excludeKeys) { + return; + } + return this.config.database + .loadSchema() + .then(schemaController => schemaController.getOneSchema(this.className)) + .then(schema => { + const fields = this.keys ? this.keys : Object.keys(schema.fields); + this.keys = fields.filter(k => this.excludeKeys.indexOf(k) < 0); + }); +}; + // Augments this.response with data at the paths provided in this.include. RestQuery.prototype.handleInclude = function() { if (this.include.length == 0) { diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index 8c6094fe72..056a8df207 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -165,6 +165,7 @@ export class ClassesRouter extends PromiseRouter { 'order', 'count', 'keys', + 'excludeKeys', 'include', 'includeAll', 'redirectClassNameForKey', @@ -200,6 +201,9 @@ export class ClassesRouter extends PromiseRouter { if (typeof body.keys == 'string') { options.keys = body.keys; } + if (typeof body.excludeKeys == 'string') { + options.excludeKeys = body.excludeKeys; + } if (body.include) { options.include = String(body.include); } From 31debc171364cff539142f99db495b4ed4eae39e Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 10 Jul 2019 12:47:15 -0500 Subject: [PATCH 2/2] improve tests --- spec/ParseQuery.spec.js | 190 +++++++++++++++++++++++++--------------- src/RestQuery.js | 14 +-- 2 files changed, 126 insertions(+), 78 deletions(-) diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 0151b9ed91..1c8adf342d 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -3266,8 +3266,8 @@ describe('Parse.Query testing', () => { ); }); - it('select/unselect keys query', function(done) { - const obj = new TestObject({ foo: 'baz', bar: 1, wooz: [1, 2, 3] }); + it('select keys query', function(done) { + const obj = new TestObject({ foo: 'baz', bar: 1 }); obj .save() @@ -3288,17 +3288,11 @@ describe('Parse.Query testing', () => { undefined, "expected 'bar' field to be unset" ); - strictEqual( - result.get('wooz'), - undefined, - "expected 'wooz' field to be unset" - ); return result.fetch(); }) .then(function(result) { strictEqual(result.get('foo'), 'baz'); strictEqual(result.get('bar'), 1); - strictEqual(result.get('wooz')[0], 1); }) .then(function() { obj._clearServerData(); @@ -3319,11 +3313,6 @@ describe('Parse.Query testing', () => { undefined, "expected 'bar' field to be unset" ); - strictEqual( - result.get('wooz'), - undefined, - "expected 'wooz' field to be unset" - ); }) .then(function() { obj._clearServerData(); @@ -3336,11 +3325,6 @@ describe('Parse.Query testing', () => { ok(!result.dirty(), 'expected result not to be dirty'); strictEqual(result.get('foo'), 'baz'); strictEqual(result.get('bar'), 1); - strictEqual( - result.get('wooz'), - undefined, - "expected 'wooz' field to be unset" - ); }) .then(function() { obj._clearServerData(); @@ -3353,60 +3337,6 @@ describe('Parse.Query testing', () => { ok(!result.dirty(), 'expected result not to be dirty'); strictEqual(result.get('foo'), 'baz'); strictEqual(result.get('bar'), 1); - strictEqual( - result.get('wooz'), - undefined, - "expected 'wooz' field to be unset" - ); - }) - .then(function() { - obj._clearServerData(); - return request({ - url: Parse.serverURL + '/classes/TestObject', - qs: { - excludeKeys: 'bar,wooz', - }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Javascript-Key': Parse.javaScriptKey, - 'Content-Type': 'application/json', - }, - }); - }) - .then(function(result) { - result = result.data.results[0]; - strictEqual(result.foo, 'baz'); - strictEqual(result.bar, undefined, "expected 'bar' field to be unset"); - strictEqual( - result.wooz, - undefined, - "expected 'wooz' field to be unset" - ); - }) - .then(function() { - obj._clearServerData(); - return request({ - url: Parse.serverURL + '/classes/TestObject', - qs: { - keys: 'foo', - excludeKeys: 'foo', - }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Javascript-Key': Parse.javaScriptKey, - 'Content-Type': 'application/json', - }, - }); - }) - .then(function(result) { - result = result.data.results[0]; - strictEqual(result.foo, undefined, "expected 'foo' field to be unset"); - strictEqual(result.bar, undefined, "expected 'bar' field to be unset"); - strictEqual( - result.wooz, - undefined, - "expected 'wooz' field to be unset" - ); }) .then( function() { @@ -3418,6 +3348,122 @@ describe('Parse.Query testing', () => { } ); }); + it('exclude keys', async () => { + const obj = new TestObject({ foo: 'baz', hello: 'world' }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + excludeKeys: 'foo', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].foo).toBeUndefined(); + expect(response.data.results[0].hello).toBe('world'); + }); + + it('exclude keys with select same key', async () => { + const obj = new TestObject({ foo: 'baz', hello: 'world' }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + keys: 'foo', + excludeKeys: 'foo', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].foo).toBeUndefined(); + expect(response.data.results[0].hello).toBeUndefined(); + }); + + it('exclude keys with select different key', async () => { + const obj = new TestObject({ foo: 'baz', hello: 'world' }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + keys: 'foo,hello', + excludeKeys: 'foo', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].foo).toBeUndefined(); + expect(response.data.results[0].hello).toBe('world'); + }); + + it('exclude keys with include same key', async () => { + const pointer = new TestObject(); + await pointer.save(); + const obj = new TestObject({ child: pointer, hello: 'world' }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + include: 'child', + excludeKeys: 'child', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].child).toBeUndefined(); + expect(response.data.results[0].hello).toBe('world'); + }); + + it('exclude keys with include different key', async () => { + const pointer = new TestObject(); + await pointer.save(); + const obj = new TestObject({ + child1: pointer, + child2: pointer, + hello: 'world', + }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + include: 'child1,child2', + excludeKeys: 'child1', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].child1).toBeUndefined(); + expect(response.data.results[0].child2.objectId).toEqual(pointer.id); + expect(response.data.results[0].hello).toBe('world'); + }); + + it('exclude keys with includeAll', async () => { + const pointer = new TestObject(); + await pointer.save(); + const obj = new TestObject({ + child1: pointer, + child2: pointer, + hello: 'world', + }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + includeAll: true, + excludeKeys: 'child1', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].child).toBeUndefined(); + expect(response.data.results[0].child2.objectId).toEqual(pointer.id); + expect(response.data.results[0].hello).toBe('world'); + }); it('select keys with each query', function(done) { const obj = new TestObject({ foo: 'baz', bar: 1 }); diff --git a/src/RestQuery.js b/src/RestQuery.js index 3fb18f2d6c..0ff3c63f23 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -711,24 +711,26 @@ RestQuery.prototype.handleIncludeAll = function() { this.include = [...new Set([...this.include, ...includeFields])]; // if this.keys not set, then all keys are already included if (this.keys) { - this.keys = [...new Set([...this.keys, ...keyFields])] - // Ignore excluded keys - .filter(k => !this.excludeKeys || this.excludeKeys.indexOf(k) < 0); + this.keys = [...new Set([...this.keys, ...keyFields])]; } }); }; -// Upadates property `this.keys` to contain all keys but the ones unselected. +// Updates property `this.keys` to contain all keys but the ones unselected. RestQuery.prototype.handleExcludeKeys = function() { if (!this.excludeKeys) { return; } + if (this.keys) { + this.keys = this.keys.filter(k => !this.excludeKeys.includes(k)); + return; + } return this.config.database .loadSchema() .then(schemaController => schemaController.getOneSchema(this.className)) .then(schema => { - const fields = this.keys ? this.keys : Object.keys(schema.fields); - this.keys = fields.filter(k => this.excludeKeys.indexOf(k) < 0); + const fields = Object.keys(schema.fields); + this.keys = fields.filter(k => !this.excludeKeys.includes(k)); }); };