From 2118a1942dd35170bd59da558de5ef2efe75da0a Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 9 Mar 2021 17:03:46 -0600 Subject: [PATCH 1/6] PG: Properly initialize database --- package-lock.json | 87 +++++++++++++++---- package.json | 1 + spec/ParseAPI.spec.js | 2 + spec/ParseServerRESTController.spec.js | 5 -- spec/PostgresStorageAdapter.spec.js | 5 +- spec/Schema.spec.js | 1 + spec/batch.spec.js | 5 -- spec/helper.js | 23 ++--- spec/schemas.spec.js | 7 +- .../Storage/Postgres/PostgresClient.js | 5 ++ .../Postgres/PostgresStorageAdapter.js | 8 +- src/Controllers/DatabaseController.js | 10 +-- src/ParseServer.js | 6 +- 13 files changed, 102 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index eef6319cec..1c26883fa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4672,7 +4672,6 @@ "version": "0.10.53", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dev": true, "requires": { "es6-iterator": "~2.0.3", "es6-symbol": "~3.1.3", @@ -4689,7 +4688,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -4700,7 +4698,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, "requires": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -4725,7 +4722,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, "requires": { "d": "^1.0.1", "ext": "^1.1.2" @@ -4735,7 +4731,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, "requires": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -5070,7 +5065,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, "requires": { "d": "1", "es5-ext": "~0.10.14" @@ -5080,7 +5074,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, "requires": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -5232,7 +5225,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "dev": true, "requires": { "type": "^2.0.0" }, @@ -5240,8 +5232,7 @@ "type": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", - "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==", - "dev": true + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" } } }, @@ -8281,7 +8272,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dev": true, "requires": { "es5-ext": "~0.10.2" } @@ -8917,8 +8907,7 @@ "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, "nice-try": { "version": "1.0.5", @@ -9611,6 +9600,74 @@ "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.2.tgz", "integrity": "sha512-1KdmFGGTP6jplJoI8MfvRlfvMiyBivMRP7/ffh4a11RUFJ7kC2J0ZHlipoKiH/1hz+DVgceon9U2qbaHpPeyPg==" }, + "pg-monitor": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/pg-monitor/-/pg-monitor-1.4.1.tgz", + "integrity": "sha512-HLLPPC/2yn7+fOpvatAAMFQMzk/8JwaVS7ATp70Uy+7aTiTvlzIqsTixc6E14L/EGk8fvhu/FJ7qiFbxygbwIA==", + "requires": { + "cli-color": "2.0.0" + }, + "dependencies": { + "cli-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz", + "integrity": "sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A==", + "requires": { + "ansi-regex": "^2.1.1", + "d": "^1.0.1", + "es5-ext": "^0.10.51", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.7" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + } + }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + } + } + }, "pg-pool": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz", @@ -11241,7 +11298,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dev": true, "requires": { "es5-ext": "~0.10.46", "next-tick": "1" @@ -11401,8 +11457,7 @@ "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "type-check": { "version": "0.3.2", diff --git a/package.json b/package.json index 456cb47484..c68305cf93 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "mongodb": "3.6.3", "mustache": "4.1.0", "parse": "3.1.0", + "pg-monitor": "1.4.1", "pg-promise": "10.9.2", "pluralize": "8.0.0", "redis": "3.0.2", diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 39aed06206..2eab8cfde1 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -170,6 +170,7 @@ describe('miscellaneous', function () { const config = Config.get('test'); // Remove existing data to clear out unique index TestUtils.destroyAllDataPermanently() + .then(() => config.database.adapter.performInitialization({ VolatileClassesSchemas: [] })) .then(() => config.database.adapter.createClass('_User', userSchema)) .then(() => config.database.adapter @@ -210,6 +211,7 @@ describe('miscellaneous', function () { const config = Config.get('test'); // Remove existing data to clear out unique index TestUtils.destroyAllDataPermanently() + .then(() => config.database.adapter.performInitialization({ VolatileClassesSchemas: [] })) .then(() => config.database.adapter.createClass('_User', userSchema)) .then(() => config.database.adapter.createObject('_User', userSchema, { diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index e2f49c0bbe..87f3ffe81b 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -2,7 +2,6 @@ const ParseServerRESTController = require('../lib/ParseServerRESTController') .ParseServerRESTController; const ParseServer = require('../lib/ParseServer').default; const Parse = require('parse/node').Parse; -const TestUtils = require('../lib/TestUtils'); const semver = require('semver'); let RESTController; @@ -183,10 +182,6 @@ describe('ParseServerRESTController', () => { } }); - beforeEach(async () => { - await TestUtils.destroyAllDataPermanently(true); - }); - it('should handle a batch request with transaction = true', done => { const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections myObject diff --git a/spec/PostgresStorageAdapter.spec.js b/spec/PostgresStorageAdapter.spec.js index 6746ea16a4..44eb064012 100644 --- a/spec/PostgresStorageAdapter.spec.js +++ b/spec/PostgresStorageAdapter.spec.js @@ -19,10 +19,11 @@ const dropTable = (client, className) => { describe_only_db('postgres')('PostgresStorageAdapter', () => { let adapter; - beforeEach(() => { + beforeEach(async () => { const config = Config.get('test'); adapter = config.database.adapter; - return adapter.deleteAllClasses(); + await adapter.deleteAllClasses(); + await adapter.performInitialization({ VolatileClassesSchemas: [] }); }); it('schemaUpgrade, upgrade the database schema when schema changes', done => { diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index 932eec16d9..bb1cace408 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -28,6 +28,7 @@ describe('SchemaController', () => { afterEach(async () => { await config.database.schemaCache.clear(); await TestUtils.destroyAllDataPermanently(false); + await config.database.adapter.performInitialization({ VolatileClassesSchemas: [] }); }); it('can validate one object', done => { diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 07dc955664..9af8b059aa 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -1,6 +1,5 @@ const batch = require('../lib/batch'); const request = require('../lib/request'); -const TestUtils = require('../lib/TestUtils'); const semver = require('semver'); const originalURL = '/parse/batch'; @@ -187,10 +186,6 @@ describe('batch', () => { } }); - beforeEach(async () => { - await TestUtils.destroyAllDataPermanently(true); - }); - it('should handle a batch request with transaction = true', done => { const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections myObject diff --git a/spec/helper.js b/spec/helper.js index a06f3da708..dc28ecdc76 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -167,7 +167,7 @@ const reconfigureServer = changedConfiguration => { const Parse = require('parse/node'); Parse.serverURL = 'http://localhost:' + port + '/1'; -beforeEach(done => { +beforeEach(async () => { try { Parse.User.enableUnsafeCurrentUser(); } catch (error) { @@ -175,23 +175,10 @@ beforeEach(done => { throw error; } } - TestUtils.destroyAllDataPermanently(true) - .catch(error => { - // For tests that connect to their own mongo, there won't be any data to delete. - if (error.message === 'ns not found' || error.message.startsWith('connect ECONNREFUSED')) { - return; - } else { - fail(error); - return; - } - }) - .then(reconfigureServer) - .then(() => { - Parse.initialize('test', 'test', 'test'); - Parse.serverURL = 'http://localhost:' + port + '/1'; - done(); - }) - .catch(done.fail); + await reconfigureServer(); + + Parse.initialize('test', 'test', 'test'); + Parse.serverURL = 'http://localhost:' + port + '/1'; }); afterEach(function (done) { diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 8442a5796a..994864ab0f 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -147,6 +147,7 @@ describe('schemas', () => { afterEach(async () => { await config.database.schemaCache.clear(); await TestUtils.destroyAllDataPermanently(false); + await config.database.adapter.performInitialization({ VolatileClassesSchemas: [] }); }); it('requires the master key to get all schemas', done => { @@ -2816,7 +2817,11 @@ describe('schemas', () => { }); describe('index management', () => { - beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently()); + beforeEach(async () => { + await TestUtils.destroyAllDataPermanently(false); + await config.database.adapter.performInitialization({ VolatileClassesSchemas: [] }); + }); + it('cannot create index if field does not exist', done => { request({ url: 'http://localhost:8378/1/schemas/NewClass', diff --git a/src/Adapters/Storage/Postgres/PostgresClient.js b/src/Adapters/Storage/Postgres/PostgresClient.js index 86fe178510..bbae91f5e4 100644 --- a/src/Adapters/Storage/Postgres/PostgresClient.js +++ b/src/Adapters/Storage/Postgres/PostgresClient.js @@ -18,6 +18,11 @@ export function createClient(uri, databaseOptions) { const pgp = require('pg-promise')(initOptions); const client = pgp(dbOptions); + if (process.env.PARSE_SERVER_LOG_LEVEL === 'debug') { + const monitor = require('pg-monitor'); + monitor.attach(initOptions); + } + if (dbOptions.pgOptions) { for (const key in dbOptions.pgOptions) { pgp.pg.defaults[key] = dbOptions.pgOptions[key]; diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index acacbac048..baf12f9316 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -852,9 +852,7 @@ export class PostgresStorageAdapter implements StorageAdapter { } async setClassLevelPermissions(className: string, CLPs: any) { - const self = this; await this._client.task('set-class-level-permissions', async t => { - await self._ensureSchemaCollectionExists(t); const values = [className, 'schema', 'classLevelPermissions', JSON.stringify(CLPs)]; await t.none( `UPDATE "_SCHEMA" SET $2:name = json_object_set_key($2:name, $3::text, $4::jsonb) WHERE "className" = $1`, @@ -917,7 +915,6 @@ export class PostgresStorageAdapter implements StorageAdapter { if (deletedIndexes.length > 0) { await self.dropIndexes(className, deletedIndexes, t); } - await self._ensureSchemaCollectionExists(t); await t.none( 'UPDATE "_SCHEMA" SET $2:name = json_object_set_key($2:name, $3::text, $4::jsonb) WHERE "className" = $1', [className, 'schema', 'indexes', JSON.stringify(existingIndexes)] @@ -948,7 +945,6 @@ export class PostgresStorageAdapter implements StorageAdapter { // Just create a table, do not insert in schema async createTable(className: string, schema: SchemaType, conn: any) { conn = conn || this._client; - const self = this; debug('createTable', className, schema); const valuesArray = []; const patternsArray = []; @@ -990,7 +986,6 @@ export class PostgresStorageAdapter implements StorageAdapter { debug(qs, values); return conn.task('create-table', async t => { try { - await self._ensureSchemaCollectionExists(t); await t.none(qs, values); } catch (error) { if (error.code !== PostgresDuplicateRelationError) { @@ -1185,9 +1180,7 @@ export class PostgresStorageAdapter implements StorageAdapter { // schemas cannot be retrieved, returns a promise that rejects. Requirements for the // rejection reason are TBD. async getAllClasses() { - const self = this; return this._client.task('get-all-classes', async t => { - await self._ensureSchemaCollectionExists(t); return await t.map('SELECT * FROM "_SCHEMA"', null, row => toParseSchema({ className: row.className, ...row.schema }) ); @@ -2244,6 +2237,7 @@ export class PostgresStorageAdapter implements StorageAdapter { async performInitialization({ VolatileClassesSchemas }: any) { // TODO: This method needs to be rewritten to make proper use of connections (@vitaly-t) debug('performInitialization'); + await this._ensureSchemaCollectionExists(); const promises = VolatileClassesSchemas.map(schema => { return this.createTable(schema.className, schema) .catch(err => { diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 6b132dcbe0..baf151686e 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -1666,7 +1666,10 @@ class DatabaseController { // TODO: create indexes on first creation of a _User object. Otherwise it's impossible to // have a Parse app without it having a _User collection. - performInitialization() { + async performInitialization() { + await this.adapter.performInitialization({ + VolatileClassesSchemas: SchemaController.VolatileClassesSchemas, + }); const requiredUserFields = { fields: { ...SchemaController.defaultColumns._Default, @@ -1777,10 +1780,6 @@ class DatabaseController { const indexPromise = this.adapter.updateSchemaWithIndexes(); - // Create tables for volatile classes - const adapterInit = this.adapter.performInitialization({ - VolatileClassesSchemas: SchemaController.VolatileClassesSchemas, - }); return Promise.all([ usernameUniqueness, usernameCaseInsensitiveIndex, @@ -1789,7 +1788,6 @@ class DatabaseController { roleUniqueness, idempotencyRequestIdIndex, idempotencyExpireIndex, - adapterInit, indexPromise, ]); } diff --git a/src/ParseServer.js b/src/ParseServer.js index a81e97fc60..d28ba178d0 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -72,11 +72,11 @@ class ParseServer { this.config = Config.put(Object.assign({}, options, allControllers)); logging.setLogger(loggerController); - const dbInitPromise = databaseController.performInitialization(); - const hooksLoadPromise = hooksController.load(); // Note: Tests will start to fail if any validation happens after this is called. - Promise.all([dbInitPromise, hooksLoadPromise]) + databaseController + .performInitialization() + .then(() => hooksController.load()) .then(() => { if (serverStartComplete) { serverStartComplete(); From 31b70bc76b26cae278acd7903b1c099373241ed4 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 9 Mar 2021 17:29:29 -0600 Subject: [PATCH 2/6] fix flaky tests --- spec/ParseGraphQLSchema.spec.js | 4 ++-- spec/ParseQuery.spec.js | 34 ++++++++++++++------------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index e756834409..983c708aa8 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -53,9 +53,9 @@ describe('ParseGraphQLSchema', () => { it('should cache schema', async () => { const graphQLSchema = await parseGraphQLSchema.load(); const updatedGraphQLSchema = await parseGraphQLSchema.load(); - expect(graphQLSchema).toBe(updatedGraphQLSchema); + expect(graphQLSchema).toEqual(updatedGraphQLSchema); await new Promise(resolve => setTimeout(resolve, 200)); - expect(graphQLSchema).toBe(await parseGraphQLSchema.load()); + expect(graphQLSchema).toEqual(await parseGraphQLSchema.load()); }); it('should load a brand new GraphQL Schema if Parse Schema changes', async () => { diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 085d178ab2..97c81f86cb 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -275,32 +275,26 @@ describe('Parse.Query testing', () => { }); }); - it('query with limit equal to maxlimit', function (done) { + it('query with limit equal to maxlimit', async () => { const baz = new TestObject({ foo: 'baz' }); const qux = new TestObject({ foo: 'qux' }); - reconfigureServer({ maxLimit: 1 }); - Parse.Object.saveAll([baz, qux]).then(function () { - const query = new Parse.Query(TestObject); - query.limit(1); - query.find().then(function (results) { - equal(results.length, 1); - done(); - }); - }); + await reconfigureServer({ maxLimit: 1 }); + await Parse.Object.saveAll([baz, qux]); + const query = new Parse.Query(TestObject); + query.limit(1); + const results = await query.find(); + equal(results.length, 1); }); - it('query with limit exceeding maxlimit', function (done) { + it('query with limit exceeding maxlimit', async () => { const baz = new TestObject({ foo: 'baz' }); const qux = new TestObject({ foo: 'qux' }); - reconfigureServer({ maxLimit: 1 }); - Parse.Object.saveAll([baz, qux]).then(function () { - const query = new Parse.Query(TestObject); - query.limit(2); - query.find().then(function (results) { - equal(results.length, 1); - done(); - }); - }); + await reconfigureServer({ maxLimit: 1 }); + await Parse.Object.saveAll([baz, qux]); + const query = new Parse.Query(TestObject); + query.limit(2); + const results = await query.find(); + equal(results.length, 1); }); it('containedIn object array queries', function (done) { From 73f437993f6daea913e8de0e69e639e9c624ba6c Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 9 Mar 2021 18:04:35 -0600 Subject: [PATCH 3/6] flaky test --- spec/ParseGraphQLSchema.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index 983c708aa8..e756834409 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -53,9 +53,9 @@ describe('ParseGraphQLSchema', () => { it('should cache schema', async () => { const graphQLSchema = await parseGraphQLSchema.load(); const updatedGraphQLSchema = await parseGraphQLSchema.load(); - expect(graphQLSchema).toEqual(updatedGraphQLSchema); + expect(graphQLSchema).toBe(updatedGraphQLSchema); await new Promise(resolve => setTimeout(resolve, 200)); - expect(graphQLSchema).toEqual(await parseGraphQLSchema.load()); + expect(graphQLSchema).toBe(await parseGraphQLSchema.load()); }); it('should load a brand new GraphQL Schema if Parse Schema changes', async () => { From d29f83b74a83d84c71e40e16b965b4086033d4f0 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 9 Mar 2021 18:31:41 -0600 Subject: [PATCH 4/6] correct test --- spec/ParseGraphQLSchema.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index e756834409..ca01c45601 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -55,7 +55,7 @@ describe('ParseGraphQLSchema', () => { const updatedGraphQLSchema = await parseGraphQLSchema.load(); expect(graphQLSchema).toBe(updatedGraphQLSchema); await new Promise(resolve => setTimeout(resolve, 200)); - expect(graphQLSchema).toBe(await parseGraphQLSchema.load()); + expect(graphQLSchema).not.toBe(await parseGraphQLSchema.load()); }); it('should load a brand new GraphQL Schema if Parse Schema changes', async () => { From 29f7ceadb48aac38a0469d2596bccee87e038dad Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 9 Mar 2021 19:42:12 -0600 Subject: [PATCH 5/6] no idea --- spec/ParseGraphQLSchema.spec.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index ca01c45601..76f00b7c9b 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -54,8 +54,6 @@ describe('ParseGraphQLSchema', () => { const graphQLSchema = await parseGraphQLSchema.load(); const updatedGraphQLSchema = await parseGraphQLSchema.load(); expect(graphQLSchema).toBe(updatedGraphQLSchema); - await new Promise(resolve => setTimeout(resolve, 200)); - expect(graphQLSchema).not.toBe(await parseGraphQLSchema.load()); }); it('should load a brand new GraphQL Schema if Parse Schema changes', async () => { From a53d8169332b3f9bb82056fe719c8d456d49d89f Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 10 Mar 2021 00:09:54 -0600 Subject: [PATCH 6/6] clean up debugger --- spec/Auth.spec.js | 6 +-- .../Postgres/PostgresStorageAdapter.js | 44 +++++++------------ 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/spec/Auth.spec.js b/spec/Auth.spec.js index 1ec2971d9d..5ed6bfe941 100644 --- a/spec/Auth.spec.js +++ b/spec/Auth.spec.js @@ -230,10 +230,10 @@ describe('Auth', () => { const role2 = new Parse.Role('role2loadtest' + i, acl2); role.getUsers().add([user]); role2.getUsers().add([user2]); - roles.push(role.save()); - roles.push(role2.save()); + roles.push(role); + roles.push(role2); } - const savedRoles = await Promise.all(roles); + const savedRoles = await Parse.Object.saveAll(roles); expect(savedRoles.length).toBe(rolesNumber * 2); const cloudRoles = await userAuth.getRolesForUser(); const cloudRoles2 = await user2Auth.getRolesForUser(); diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index baf12f9316..e3ea204be4 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -945,7 +945,7 @@ export class PostgresStorageAdapter implements StorageAdapter { // Just create a table, do not insert in schema async createTable(className: string, schema: SchemaType, conn: any) { conn = conn || this._client; - debug('createTable', className, schema); + debug('createTable'); const valuesArray = []; const patternsArray = []; const fields = Object.assign({}, schema.fields); @@ -983,7 +983,6 @@ export class PostgresStorageAdapter implements StorageAdapter { const qs = `CREATE TABLE IF NOT EXISTS $1:name (${patternsArray.join()})`; const values = [className, ...valuesArray]; - debug(qs, values); return conn.task('create-table', async t => { try { await t.none(qs, values); @@ -1007,7 +1006,7 @@ export class PostgresStorageAdapter implements StorageAdapter { } async schemaUpgrade(className: string, schema: SchemaType, conn: any) { - debug('schemaUpgrade', { className, schema }); + debug('schemaUpgrade'); conn = conn || this._client; const self = this; @@ -1029,7 +1028,7 @@ export class PostgresStorageAdapter implements StorageAdapter { async addFieldIfNotExists(className: string, fieldName: string, type: any, conn: any) { // TODO: Must be revised for invalid logic... - debug('addFieldIfNotExists', { className, fieldName, type }); + debug('addFieldIfNotExists'); conn = conn || this._client; const self = this; await conn.tx('add-field-if-not-exists', async t => { @@ -1148,7 +1147,7 @@ export class PostgresStorageAdapter implements StorageAdapter { // Returns a Promise. async deleteFields(className: string, schema: SchemaType, fieldNames: string[]): Promise { - debug('deleteFields', className, fieldNames); + debug('deleteFields'); fieldNames = fieldNames.reduce((list: Array, fieldName: string) => { const field = schema.fields[fieldName]; if (field.type !== 'Relation') { @@ -1191,7 +1190,7 @@ export class PostgresStorageAdapter implements StorageAdapter { // this adapter doesn't know about the schema, return a promise that rejects with // undefined as the reason. async getClass(className: string) { - debug('getClass', className); + debug('getClass'); return this._client .any('SELECT * FROM "_SCHEMA" WHERE "className" = $', { className, @@ -1212,7 +1211,7 @@ export class PostgresStorageAdapter implements StorageAdapter { object: any, transactionalSession: ?any ) { - debug('createObject', className, object); + debug('createObject'); let columnsArray = []; const valuesArray = []; schema = toPostgresSchema(schema); @@ -1333,7 +1332,6 @@ export class PostgresStorageAdapter implements StorageAdapter { const qs = `INSERT INTO $1:name (${columnsPattern}) VALUES (${valuesPattern})`; const values = [className, ...columnsArray, ...valuesArray]; - debug(qs, values); const promise = (transactionalSession ? transactionalSession.t : this._client) .none(qs, values) .then(() => ({ ops: [object] })) @@ -1369,7 +1367,7 @@ export class PostgresStorageAdapter implements StorageAdapter { query: QueryType, transactionalSession: ?any ) { - debug('deleteObjectsByQuery', className, query); + debug('deleteObjectsByQuery'); const values = [className]; const index = 2; const where = buildWhereClause({ @@ -1383,7 +1381,6 @@ export class PostgresStorageAdapter implements StorageAdapter { where.pattern = 'TRUE'; } const qs = `WITH deleted AS (DELETE FROM $1:name WHERE ${where.pattern} RETURNING *) SELECT count(*) FROM deleted`; - debug(qs, values); const promise = (transactionalSession ? transactionalSession.t : this._client) .one(qs, values, a => +a.count) .then(count => { @@ -1412,7 +1409,7 @@ export class PostgresStorageAdapter implements StorageAdapter { update: any, transactionalSession: ?any ): Promise { - debug('findOneAndUpdate', className, query, update); + debug('findOneAndUpdate'); return this.updateObjectsByQuery(className, schema, query, update, transactionalSession).then( val => val[0] ); @@ -1426,7 +1423,7 @@ export class PostgresStorageAdapter implements StorageAdapter { update: any, transactionalSession: ?any ): Promise<[any]> { - debug('updateObjectsByQuery', className, query, update); + debug('updateObjectsByQuery'); const updatePatterns = []; const values = [className]; let index = 2; @@ -1651,7 +1648,7 @@ export class PostgresStorageAdapter implements StorageAdapter { index += 2; } } else { - debug('Not supported update', fieldName, fieldValue); + debug('Not supported update', { fieldName, fieldValue }); return Promise.reject( new Parse.Error( Parse.Error.OPERATION_FORBIDDEN, @@ -1671,7 +1668,6 @@ export class PostgresStorageAdapter implements StorageAdapter { const whereClause = where.pattern.length > 0 ? `WHERE ${where.pattern}` : ''; const qs = `UPDATE $1:name SET ${updatePatterns.join()} ${whereClause} RETURNING *`; - debug('update: ', qs, values); const promise = (transactionalSession ? transactionalSession.t : this._client).any(qs, values); if (transactionalSession) { transactionalSession.batch.push(promise); @@ -1687,7 +1683,7 @@ export class PostgresStorageAdapter implements StorageAdapter { update: any, transactionalSession: ?any ) { - debug('upsertOneObject', { className, query, update }); + debug('upsertOneObject'); const createValue = Object.assign({}, query, update); return this.createObject(className, schema, createValue, transactionalSession).catch(error => { // ignore duplicate value errors as it's upsert @@ -1704,14 +1700,7 @@ export class PostgresStorageAdapter implements StorageAdapter { query: QueryType, { skip, limit, sort, keys, caseInsensitive, explain }: QueryOptions ) { - debug('find', className, query, { - skip, - limit, - sort, - keys, - caseInsensitive, - explain, - }); + debug('find'); const hasLimit = limit !== undefined; const hasSkip = skip !== undefined; let values = [className]; @@ -1778,7 +1767,6 @@ export class PostgresStorageAdapter implements StorageAdapter { const originalQuery = `SELECT ${columns} FROM $1:name ${wherePattern} ${sortPattern} ${limitPattern} ${skipPattern}`; const qs = explain ? this.createExplainableQuery(originalQuery) : originalQuery; - debug(qs, values); return this._client .any(qs, values) .catch(error => { @@ -1926,7 +1914,7 @@ export class PostgresStorageAdapter implements StorageAdapter { readPreference?: string, estimate?: boolean = true ) { - debug('count', className, query, readPreference, estimate); + debug('count'); const values = [className]; const where = buildWhereClause({ schema, @@ -1962,7 +1950,7 @@ export class PostgresStorageAdapter implements StorageAdapter { } async distinct(className: string, schema: SchemaType, query: QueryType, fieldName: string) { - debug('distinct', className, query); + debug('distinct'); let field = fieldName; let column = fieldName; const isNested = fieldName.indexOf('.') >= 0; @@ -1989,7 +1977,6 @@ export class PostgresStorageAdapter implements StorageAdapter { if (isNested) { qs = `SELECT DISTINCT ${transformer}($1:raw) $2:raw FROM $3:name ${wherePattern}`; } - debug(qs, values); return this._client .any(qs, values) .catch(error => { @@ -2028,7 +2015,7 @@ export class PostgresStorageAdapter implements StorageAdapter { hint: ?mixed, explain?: boolean ) { - debug('aggregate', className, pipeline, readPreference, hint, explain); + debug('aggregate'); const values = [className]; let index: number = 2; let columns: string[] = []; @@ -2209,7 +2196,6 @@ export class PostgresStorageAdapter implements StorageAdapter { .filter(Boolean) .join()} FROM $1:name ${wherePattern} ${skipPattern} ${groupPattern} ${sortPattern} ${limitPattern}`; const qs = explain ? this.createExplainableQuery(originalQuery) : originalQuery; - debug(qs, values); return this._client.any(qs, values).then(a => { if (explain) { return a;