From f75c8b3a4dddc45186b45688cd2ff43f25680768 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 6 Jun 2016 13:47:11 -0700 Subject: [PATCH] index on unique-indexes: c454180 Revert "Log objects rather than JSON stringified objects (#1922)" reconfigure username/email tests Fix broken cloud code Save callback to variable undo Fix all tests where connections are left open after server closes. Fix issues caused by missing gridstore adapter remove uses of _collection reorder find() arguments Accept a database adapter as a parameter sudo maybe? use postgres username reorder find() arguments Build objects with default fields correctly Don't tell adapter about ACL WIP --- spec/ParseAPI.spec.js | 12 ++++--- spec/ParseInstallation.spec.js | 6 ++-- spec/ParseUser.spec.js | 2 +- spec/helper.js | 2 -- .../Storage/Mongo/MongoSchemaCollection.js | 7 +++- .../Postgres/PostgresStorageAdapter.js | 33 ++++++++++++++++--- src/Controllers/SchemaController.js | 33 ++++++++++++------- 7 files changed, 68 insertions(+), 27 deletions(-) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 7bba6c9b..64d49053 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -37,19 +37,22 @@ describe('miscellaneous', function() { expect(obj2.id).toEqual(obj.id); done(); }, - error: fail + error: error => { + fail(JSON.stringify(error)); + done(); + } }); }); }); - it('create a valid parse user', function(done) { + fit('create a valid parse user', function(done) { createTestUser(function(data) { expect(data.id).not.toBeUndefined(); expect(data.getSessionToken()).not.toBeUndefined(); expect(data.get('password')).toBeUndefined(); done(); - }, function(err) { - fail(err); + }, error => { + fail(JSON.stringify(error)); done(); }); }); @@ -86,7 +89,6 @@ describe('miscellaneous', function() { }); it('ensure that email is uniquely indexed', done => { - let numCreated = 0; let numFailed = 0; let user1 = new Parse.User(); diff --git a/spec/ParseInstallation.spec.js b/spec/ParseInstallation.spec.js index 3d1e45d6..7663e9a4 100644 --- a/spec/ParseInstallation.spec.js +++ b/spec/ParseInstallation.spec.js @@ -24,7 +24,7 @@ describe('Installations', () => { 'deviceType': device }; rest.create(config, auth.nobody(config), '_Installation', input) - .then(() => config.database.adapter.find('_Installation', installationSchema, {}, {})) + .then(() => database.adapter.find('_Installation', installationSchema, {}, {})) .then(results => { expect(results.length).toEqual(1); var obj = results[0]; @@ -42,7 +42,7 @@ describe('Installations', () => { 'deviceType': device }; rest.create(config, auth.nobody(config), '_Installation', input) - .then(() => config.database.adapter.find('_Installation', installationSchema, {}, {})) + .then(() => database.adapter.find('_Installation', installationSchema, {}, {})) .then(results => { expect(results.length).toEqual(1); var obj = results[0]; @@ -60,7 +60,7 @@ describe('Installations', () => { 'deviceType': device }; rest.create(config, auth.nobody(config), '_Installation', input) - .then(() => config.database.adapter.find('_Installation', installationSchema, {}, {})) + .then(() => database.adapter.find('_Installation', installationSchema, {}, {})) .then(results => { expect(results.length).toEqual(1); var obj = results[0]; diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 1131e95c..91a3b9a3 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -2288,7 +2288,7 @@ describe('Parse.User testing', () => { expect(err.code).toEqual(209); expect(err.message).toEqual("Session token is expired."); Parse.User.logOut() // Logout to prevent polluting CLI with messages - .then(done()); + .then(done); }); }); }); diff --git a/spec/helper.js b/spec/helper.js index 472eea98..a6ed4685 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -11,7 +11,6 @@ var ParseServer = require('../src/index').ParseServer; var path = require('path'); var TestUtils = require('../src/index').TestUtils; var MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter'); - const GridStoreAdapter = require('../src/Adapters/Files/GridStoreAdapter').GridStoreAdapter; const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter'); @@ -30,7 +29,6 @@ if (process.env.PARSE_SERVER_TEST_DB === 'postgres') { }) } - var port = 8378; let gridStoreAdapter = new GridStoreAdapter(mongoURI); diff --git a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js index 0b7394ac..5b9470f3 100644 --- a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js +++ b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js @@ -1,4 +1,3 @@ - import MongoCollection from './MongoCollection'; function mongoFieldToParseSchemaField(type) { @@ -127,6 +126,12 @@ function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPe } } + // Legacy mongo adapter knows about the difference between password and _hashed_password. + // Future database adapters will only know about _hashed_password. + // Note: Parse Server will bring back password with injectDefaultSchema, so we don't need + // to add it here. + delete mongoObject._hashed_password; + return mongoObject; } diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 62f0703b..e2ade71c 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -3,6 +3,15 @@ const pgp = require('pg-promise')(); const PostgresRelationDoesNotExistError = '42P01'; const PostgresDuplicateRelationError = '42P07'; +const parseTypeToPostgresType = type => { + switch (type.type) { + case 'String': return 'text'; + case 'Date': return 'timestamp'; + case 'Object': return 'jsonb'; + case 'Boolean': return 'boolean'; + default: throw `no type for ${JSON.stringify(type)} yet`; + } +}; export class PostgresStorageAdapter { // Private @@ -37,13 +46,20 @@ export class PostgresStorageAdapter { } createClass(className, schema) { - return this._client.query('CREATE TABLE $ ()', { className }) + let valuesArray = []; + let patternsArray = []; + Object.keys(schema.fields).forEach((fieldName, index) => { + valuesArray.push(fieldName); + valuesArray.push(parseTypeToPostgresType(schema.fields[fieldName])); + patternsArray.push(`$${index * 2 + 2}:name $${index * 2 + 3}:raw`); + }); + return this._client.query(`CREATE TABLE $1:name (${patternsArray.join(',')})`, [className, ...valuesArray]) .then(() => this._client.query('INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($, $, true)', { className, schema })) } addFieldIfNotExists(className, fieldName, type) { // TODO: Doing this in a transaction is probably a good idea. - return this._client.query('ALTER TABLE "GameScore" ADD COLUMN "score" double precision', { className, fieldName }) + return this._client.query('ALTER TABLE $ ADD COLUMN $ text', { className, fieldName }) .catch(error => { if (error.code === PostgresRelationDoesNotExistError) { return this.createClass(className, { fields: { [fieldName]: type } }) @@ -136,7 +152,16 @@ export class PostgresStorageAdapter { // TODO: remove the mongo format dependency createObject(className, schema, object) { - return this._client.query('INSERT INTO "GameScore" (score) VALUES ($)', { score: object.score }) + console.log(object); + let columnsArray = []; + let valuesArray = []; + Object.keys(object).forEach(fieldName => { + columnsArray.push(fieldName); + valuesArray.push(object[fieldName]); + }); + let columnsPattern = columnsArray.map((col, index) => `$${index + 2}~`).join(','); + let valuesPattern = valuesArray.map((val, index) => `$${index + 2 + columnsArray.length}`).join(','); + return this._client.query(`INSERT INTO $1~ (${columnsPattern}) VALUES (${valuesPattern})`, [className, ...columnsArray, ...valuesArray]) .then(() => ({ ops: [object] })); } @@ -164,7 +189,7 @@ export class PostgresStorageAdapter { // Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }. find(className, schema, query, { skip, limit, sort }) { - return this._client.query("SELECT * FROM $", { className }) + return this._client.query("SELECT * FROM $", { className }) } // Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 0c550215..b8151526 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -220,6 +220,18 @@ const fieldTypeIsInvalid = ({ type, targetClass }) => { return undefined; } +const convertSchemaToAdapterSchema = schema => { + schema = injectDefaultSchema(schema); + delete schema.fields.ACL; + + if (schema.className === '_User') { + delete schema.fields.password; + schema.fields._hashed_password = { type: 'String' }; + } + + return schema; +} + const injectDefaultSchema = schema => ({ className: schema.className, fields: { @@ -293,7 +305,7 @@ class SchemaController { return Promise.reject(validationError); } - return this._dbAdapter.createClass(className, { fields, classLevelPermissions }) + return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields, classLevelPermissions, className })) .catch(error => { if (error && error.code === Parse.Error.DUPLICATE_VALUE) { throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`); @@ -360,20 +372,15 @@ class SchemaController { // Returns a promise that resolves successfully to the new schema // object or fails with a reason. - // If 'freeze' is true, refuse to modify the schema. - enforceClassExists(className, freeze) { + enforceClassExists(className) { if (this.data[className]) { return Promise.resolve(this); } - if (freeze) { - throw new Parse.Error(Parse.Error.INVALID_JSON, - 'schema is frozen, cannot add: ' + className); - } // We don't have this class. Update the schema - return this.addClassIfNotExists(className, {}).then(() => { + return this.addClassIfNotExists(className).then(() => { // The schema update succeeded. Reload the schema return this.reloadData(); - }, () => { + }, error => { // The schema update failed. This can be okay - it might // have failed because there's a race condition and a different // client is making the exact same schema update that we want. @@ -381,8 +388,12 @@ class SchemaController { return this.reloadData(); }).then(() => { // Ensure that the schema now validates - return this.enforceClassExists(className, true); - }, () => { + if (this.data[className]) { + return this; + } else { + throw new Parse.Error(Parse.Error.INVALID_JSON, `Failed to add ${className}`); + } + }, error => { // The schema still doesn't validate. Give up throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate'); });