diff --git a/spec/MongoSchemaCollectionAdapter.spec.js b/spec/MongoSchemaCollectionAdapter.spec.js index f5f75442f6..ba22c1fab3 100644 --- a/spec/MongoSchemaCollectionAdapter.spec.js +++ b/spec/MongoSchemaCollectionAdapter.spec.js @@ -21,9 +21,6 @@ describe('MongoSchemaCollection', () => { "create":{"*":true}, "delete":{"*":true}, "addField":{"*":true}, - }, - "indexes": { - "name1":{"deviceToken":1} } }, "installationId":"string", @@ -69,10 +66,7 @@ describe('MongoSchemaCollection', () => { update: { '*': true }, delete: { '*': true }, addField: { '*': true }, - }, - indexes: { - name1: {deviceToken: 1} - }, + } }); done(); }); diff --git a/spec/ParseQuery.FullTextSearch.spec.js b/spec/ParseQuery.FullTextSearch.spec.js index 2563781d6e..acb67c73f2 100644 --- a/spec/ParseQuery.FullTextSearch.spec.js +++ b/spec/ParseQuery.FullTextSearch.spec.js @@ -31,8 +31,7 @@ const fullTextHelper = () => { const request = { method: "POST", body: { - subject: subjects[i], - comment: subjects[i], + subject: subjects[i] }, path: "/1/classes/TestObject" }; @@ -281,83 +280,42 @@ describe('Parse.Query Full Text Search testing', () => { }); describe_only_db('mongo')('Parse.Query Full Text Search testing', () => { - it('fullTextSearch: does not create text index if compound index exist', (done) => { - fullTextHelper().then(() => { - return databaseAdapter.dropAllIndexes('TestObject'); - }).then(() => { - return databaseAdapter.getIndexes('TestObject'); - }).then((indexes) => { - expect(indexes.length).toEqual(1); - return databaseAdapter.createIndex('TestObject', {subject: 'text', comment: 'text'}); + it('fullTextSearch: $search, only one text index', (done) => { + return reconfigureServer({ + appId: 'test', + restAPIKey: 'test', + publicServerURL: 'http://localhost:8378/1', + databaseAdapter: new MongoStorageAdapter({ uri: mongoURI }) }).then(() => { - return databaseAdapter.getIndexes('TestObject'); - }).then((indexes) => { - expect(indexes.length).toEqual(2); - const where = { - subject: { - $text: { - $search: { - $term: 'coffee' - } - } - } - }; return rp.post({ - url: 'http://localhost:8378/1/classes/TestObject', - json: { where, '_method': 'GET' }, + url: 'http://localhost:8378/1/batch', + body: { + requests: [ + { + method: "POST", + body: { + subject: "coffee is java" + }, + path: "/1/classes/TestObject" + }, + { + method: "POST", + body: { + subject: "java is coffee" + }, + path: "/1/classes/TestObject" + } + ] + }, + json: true, headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'test' } }); - }).then((resp) => { - expect(resp.results.length).toEqual(3); - return databaseAdapter.getIndexes('TestObject'); - }).then((indexes) => { - expect(indexes.length).toEqual(2); - rp.get({ - url: 'http://localhost:8378/1/schemas/TestObject', - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, - json: true, - }, (error, response, body) => { - expect(body.indexes._id_).toBeDefined(); - expect(body.indexes._id_._id).toEqual(1); - expect(body.indexes.subject_text_comment_text).toBeDefined(); - expect(body.indexes.subject_text_comment_text.subject).toEqual('text'); - expect(body.indexes.subject_text_comment_text.comment).toEqual('text'); - done(); - }); - }).catch(done.fail); - }); - - it('fullTextSearch: does not create text index if schema compound index exist', (done) => { - fullTextHelper().then(() => { - return databaseAdapter.dropAllIndexes('TestObject'); }).then(() => { - return databaseAdapter.getIndexes('TestObject'); - }).then((indexes) => { - expect(indexes.length).toEqual(1); - return rp.put({ - url: 'http://localhost:8378/1/schemas/TestObject', - json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'test', - 'X-Parse-Master-Key': 'test', - }, - body: { - indexes: { - text_test: { subject: 'text', comment: 'text'}, - }, - }, - }); + return databaseAdapter.createIndex('TestObject', {random: 'text'}); }).then(() => { - return databaseAdapter.getIndexes('TestObject'); - }).then((indexes) => { - expect(indexes.length).toEqual(2); const where = { subject: { $text: { @@ -376,26 +334,12 @@ describe_only_db('mongo')('Parse.Query Full Text Search testing', () => { } }); }).then((resp) => { - expect(resp.results.length).toEqual(3); - return databaseAdapter.getIndexes('TestObject'); - }).then((indexes) => { - expect(indexes.length).toEqual(2); - rp.get({ - url: 'http://localhost:8378/1/schemas/TestObject', - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, - json: true, - }, (error, response, body) => { - expect(body.indexes._id_).toBeDefined(); - expect(body.indexes._id_._id).toEqual(1); - expect(body.indexes.text_test).toBeDefined(); - expect(body.indexes.text_test.subject).toEqual('text'); - expect(body.indexes.text_test.comment).toEqual('text'); - done(); - }); - }).catch(done.fail); + fail(`Should not be more than one text index: ${JSON.stringify(resp)}`); + done(); + }).catch((err) => { + expect(err.error.code).toEqual(Parse.Error.INTERNAL_SERVER_ERROR); + done(); + }); }); it('fullTextSearch: $diacriticSensitive - false', (done) => { diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index 986740650b..eef17e9751 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -274,7 +274,7 @@ describe('SchemaController', () => { fooSixteen: {type: 'String'}, fooEighteen: {type: 'String'}, fooNineteen: {type: 'String'}, - }, levelPermissions, {}, config.database)) + }, levelPermissions, config.database)) .then(actualSchema => { const expectedSchema = { className: 'NewClass', @@ -304,9 +304,6 @@ describe('SchemaController', () => { fooNineteen: {type: 'String'}, }, classLevelPermissions: { ...levelPermissions }, - indexes: { - _id_: { _id: 1 } - } }; expect(dd(actualSchema, expectedSchema)).toEqual(undefined); diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 8c7481d9bb..6219130500 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -771,7 +771,7 @@ describe('schemas', () => { }); }); - it('lets you delete multiple fields and add fields', done => { + it_exclude_dbs(['postgres'])('lets you delete multiple fields and add fields', done => { var obj1 = hasAllPODobject(); obj1.save() .then(() => { @@ -1756,605 +1756,4 @@ describe('schemas', () => { done(); }); }); - - it('cannot create index if field does not exist', done => { - request.post({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: {}, - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - indexes: { - name1: { aString: 1}, - } - } - }, (error, response, body) => { - expect(body.code).toBe(Parse.Error.INVALID_QUERY); - expect(body.error).toBe('Field aString does not exist, cannot add index.'); - done(); - }); - }) - }); - - it('cannot create compound index if field does not exist', done => { - request.post({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: {}, - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - fields: { - aString: {type: 'String'} - }, - indexes: { - name1: { aString: 1, bString: 1}, - } - } - }, (error, response, body) => { - expect(body.code).toBe(Parse.Error.INVALID_QUERY); - expect(body.error).toBe('Field bString does not exist, cannot add index.'); - done(); - }); - }) - }); - - it('allows add index when you create a class', done => { - request.post({ - url: 'http://localhost:8378/1/schemas', - headers: masterKeyHeaders, - json: true, - body: { - className: "NewClass", - fields: { - aString: {type: 'String'} - }, - indexes: { - name1: { aString: 1}, - }, - } - }, (error, response, body) => { - expect(body).toEqual({ - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'} - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - name1: { aString: 1}, - }, - }); - config.database.adapter.getIndexes('NewClass').then((indexes) => { - expect(indexes.length).toBe(2); - done(); - }); - }); - }); - - it('empty index returns nothing', done => { - request.post({ - url: 'http://localhost:8378/1/schemas', - headers: masterKeyHeaders, - json: true, - body: { - className: "NewClass", - fields: { - aString: {type: 'String'} - }, - indexes: {}, - } - }, (error, response, body) => { - expect(body).toEqual({ - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'} - }, - classLevelPermissions: defaultClassLevelPermissions, - }); - done(); - }); - }); - - it('lets you add indexes', done => { - request.post({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: {}, - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - fields: { - aString: {type: 'String'} - }, - indexes: { - name1: { aString: 1}, - }, - } - }, (error, response, body) => { - expect(dd(body, { - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'} - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - name1: { aString: 1 }, - } - })).toEqual(undefined); - request.get({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - }, (error, response, body) => { - expect(body).toEqual({ - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'} - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - name1: { aString: 1 }, - } - }); - config.database.adapter.getIndexes('NewClass').then((indexes) => { - expect(indexes.length).toEqual(2); - done(); - }); - }); - }); - }) - }); - - it('lets you add multiple indexes', done => { - request.post({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: {}, - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - fields: { - aString: {type: 'String'}, - bString: {type: 'String'}, - cString: {type: 'String'}, - dString: {type: 'String'}, - }, - indexes: { - name1: { aString: 1 }, - name2: { bString: 1 }, - name3: { cString: 1, dString: 1 }, - } - } - }, (error, response, body) => { - expect(dd(body, { - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'}, - bString: {type: 'String'}, - cString: {type: 'String'}, - dString: {type: 'String'}, - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - name1: { aString: 1 }, - name2: { bString: 1 }, - name3: { cString: 1, dString: 1 }, - } - })).toEqual(undefined); - request.get({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - }, (error, response, body) => { - expect(body).toEqual({ - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'}, - bString: {type: 'String'}, - cString: {type: 'String'}, - dString: {type: 'String'}, - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - name1: { aString: 1 }, - name2: { bString: 1 }, - name3: { cString: 1, dString: 1 }, - }, - }); - config.database.adapter.getIndexes('NewClass').then((indexes) => { - expect(indexes.length).toEqual(4); - done(); - }); - }); - }); - }) - }); - - it('lets you delete indexes', done => { - request.post({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: {}, - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - fields: { - aString: {type: 'String'}, - }, - indexes: { - name1: { aString: 1 }, - } - } - }, (error, response, body) => { - expect(dd(body, { - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'}, - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - name1: { aString: 1 }, - } - })).toEqual(undefined); - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - indexes: { - name1: { __op: 'Delete' } - } - } - }, (error, response, body) => { - expect(body).toEqual({ - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'}, - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - } - }); - config.database.adapter.getIndexes('NewClass').then((indexes) => { - expect(indexes.length).toEqual(1); - done(); - }); - }); - }); - }) - }); - - it('lets you delete multiple indexes', done => { - request.post({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: {}, - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - fields: { - aString: {type: 'String'}, - bString: {type: 'String'}, - cString: {type: 'String'}, - }, - indexes: { - name1: { aString: 1 }, - name2: { bString: 1 }, - name3: { cString: 1 }, - } - } - }, (error, response, body) => { - expect(dd(body, { - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'}, - bString: {type: 'String'}, - cString: {type: 'String'}, - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - name1: { aString: 1 }, - name2: { bString: 1 }, - name3: { cString: 1 }, - } - })).toEqual(undefined); - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - indexes: { - name1: { __op: 'Delete' }, - name2: { __op: 'Delete' }, - } - } - }, (error, response, body) => { - expect(body).toEqual({ - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'}, - bString: {type: 'String'}, - cString: {type: 'String'}, - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - name3: { cString: 1 }, - } - }); - config.database.adapter.getIndexes('NewClass').then((indexes) => { - expect(indexes.length).toEqual(2); - done(); - }); - }); - }); - }) - }); - - it('lets you add and delete indexes', done => { - request.post({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: {}, - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - fields: { - aString: {type: 'String'}, - bString: {type: 'String'}, - cString: {type: 'String'}, - dString: {type: 'String'}, - }, - indexes: { - name1: { aString: 1 }, - name2: { bString: 1 }, - name3: { cString: 1 }, - } - } - }, (error, response, body) => { - expect(dd(body, { - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'}, - bString: {type: 'String'}, - cString: {type: 'String'}, - dString: {type: 'String'}, - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - name1: { aString: 1 }, - name2: { bString: 1 }, - name3: { cString: 1 }, - } - })).toEqual(undefined); - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - indexes: { - name1: { __op: 'Delete' }, - name2: { __op: 'Delete' }, - name4: { dString: 1 }, - } - } - }, (error, response, body) => { - expect(body).toEqual({ - className: 'NewClass', - fields: { - ACL: {type: 'ACL'}, - createdAt: {type: 'Date'}, - updatedAt: {type: 'Date'}, - objectId: {type: 'String'}, - aString: {type: 'String'}, - bString: {type: 'String'}, - cString: {type: 'String'}, - dString: {type: 'String'}, - }, - classLevelPermissions: defaultClassLevelPermissions, - indexes: { - _id_: { _id: 1 }, - name3: { cString: 1 }, - name4: { dString: 1 }, - } - }); - config.database.adapter.getIndexes('NewClass').then((indexes) => { - expect(indexes.length).toEqual(3); - done(); - }); - }); - }); - }) - }); - - it('cannot delete index that does not exist', done => { - request.post({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: {}, - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - indexes: { - unknownIndex: { __op: 'Delete' } - } - } - }, (error, response, body) => { - expect(body.code).toBe(Parse.Error.INVALID_QUERY); - expect(body.error).toBe('Index unknownIndex does not exist, cannot delete.'); - done(); - }); - }) - }); - - it('cannot update index that exist', done => { - request.post({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: {}, - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - fields: { - aString: {type: 'String'}, - }, - indexes: { - name1: { aString: 1 } - } - } - }, () => { - request.put({ - url: 'http://localhost:8378/1/schemas/NewClass', - headers: masterKeyHeaders, - json: true, - body: { - indexes: { - name1: { field2: 1 } - } - } - }, (error, response, body) => { - expect(body.code).toBe(Parse.Error.INVALID_QUERY); - expect(body.error).toBe('Index name1 exists, cannot update.'); - done(); - }); - }); - }) - }); - - it_exclude_dbs(['postgres'])('get indexes on startup', (done) => { - const obj = new Parse.Object('TestObject'); - obj.save().then(() => { - return reconfigureServer({ - appId: 'test', - restAPIKey: 'test', - publicServerURL: 'http://localhost:8378/1', - }); - }).then(() => { - request.get({ - url: 'http://localhost:8378/1/schemas/TestObject', - headers: masterKeyHeaders, - json: true, - }, (error, response, body) => { - expect(body.indexes._id_).toBeDefined(); - done(); - }); - }); - }); - - it_exclude_dbs(['postgres'])('get compound indexes on startup', (done) => { - const obj = new Parse.Object('TestObject'); - obj.set('subject', 'subject'); - obj.set('comment', 'comment'); - obj.save().then(() => { - return config.database.adapter.createIndex('TestObject', {subject: 'text', comment: 'text'}); - }).then(() => { - return reconfigureServer({ - appId: 'test', - restAPIKey: 'test', - publicServerURL: 'http://localhost:8378/1', - }); - }).then(() => { - request.get({ - url: 'http://localhost:8378/1/schemas/TestObject', - headers: masterKeyHeaders, - json: true, - }, (error, response, body) => { - expect(body.indexes._id_).toBeDefined(); - expect(body.indexes._id_._id).toEqual(1); - expect(body.indexes.subject_text_comment_text).toBeDefined(); - expect(body.indexes.subject_text_comment_text.subject).toEqual('text'); - expect(body.indexes.subject_text_comment_text.comment).toEqual('text'); - done(); - }); - }); - }); }); diff --git a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js index 7de184250d..051bac65cf 100644 --- a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js +++ b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js @@ -63,20 +63,13 @@ const defaultCLPS = Object.freeze({ function mongoSchemaToParseSchema(mongoSchema) { let clps = defaultCLPS; - let indexes = {} - if (mongoSchema._metadata) { - if (mongoSchema._metadata.class_permissions) { - clps = {...emptyCLPS, ...mongoSchema._metadata.class_permissions}; - } - if (mongoSchema._metadata.indexes) { - indexes = {...mongoSchema._metadata.indexes}; - } + if (mongoSchema._metadata && mongoSchema._metadata.class_permissions) { + clps = {...emptyCLPS, ...mongoSchema._metadata.class_permissions}; } return { className: mongoSchema._id, fields: mongoSchemaFieldsToParseSchemaFields(mongoSchema), classLevelPermissions: clps, - indexes: indexes, }; } diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index cd70cf9bb5..c9b42f315b 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -53,7 +53,7 @@ const convertParseSchemaToMongoSchema = ({...schema}) => { // Returns { code, error } if invalid, or { result }, an object // suitable for inserting into _SCHEMA collection, otherwise. -const mongoSchemaFromFieldsAndClassNameAndCLP = (fields, className, classLevelPermissions, indexes) => { +const mongoSchemaFromFieldsAndClassNameAndCLP = (fields, className, classLevelPermissions) => { const mongoObject = { _id: className, objectId: 'string', @@ -74,11 +74,6 @@ const mongoSchemaFromFieldsAndClassNameAndCLP = (fields, className, classLevelPe } } - if (indexes && typeof indexes === 'object' && Object.keys(indexes).length > 0) { - mongoObject._metadata = mongoObject._metadata || {}; - mongoObject._metadata.indexes = indexes; - } - return mongoObject; } @@ -170,81 +165,11 @@ export class MongoStorageAdapter { })); } - setIndexesWithSchemaFormat(className, submittedIndexes, existingIndexes = {}, fields) { - if (submittedIndexes === undefined) { - return Promise.resolve(); - } - if (Object.keys(existingIndexes).length === 0) { - existingIndexes = { _id_: { _id: 1} }; - } - const deletePromises = []; - const insertedIndexes = []; - Object.keys(submittedIndexes).forEach(name => { - const field = submittedIndexes[name]; - if (existingIndexes[name] && field.__op !== 'Delete') { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Index ${name} exists, cannot update.`); - } - if (!existingIndexes[name] && field.__op === 'Delete') { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Index ${name} does not exist, cannot delete.`); - } - if (field.__op === 'Delete') { - const promise = this.dropIndex(className, name); - deletePromises.push(promise); - delete existingIndexes[name]; - } else { - Object.keys(field).forEach(key => { - if (!fields.hasOwnProperty(key)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Field ${key} does not exist, cannot add index.`); - } - }); - existingIndexes[name] = field; - insertedIndexes.push({ - key: field, - name, - }); - } - }); - let insertPromise = Promise.resolve(); - if (insertedIndexes.length > 0) { - insertPromise = this.createIndexes(className, insertedIndexes); - } - return Promise.all(deletePromises) - .then(() => insertPromise) - .then(() => this._schemaCollection()) - .then(schemaCollection => schemaCollection.updateSchema(className, { - $set: { _metadata: { indexes: existingIndexes } } - })); - } - - setIndexesFromMongo(className) { - return this.getIndexes(className).then((indexes) => { - indexes = indexes.reduce((obj, index) => { - if (index.key._fts) { - delete index.key._fts; - delete index.key._ftsx; - for (const field in index.weights) { - index.key[field] = 'text'; - } - } - obj[index.name] = index.key; - return obj; - }, {}); - return this._schemaCollection() - .then(schemaCollection => schemaCollection.updateSchema(className, { - $set: { _metadata: { indexes: indexes } } - })); - }).catch(() => { - // Ignore if collection not found - return Promise.resolve(); - }); - } - createClass(className, schema) { schema = convertParseSchemaToMongoSchema(schema); - const mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(schema.fields, className, schema.classLevelPermissions, schema.indexes); + const mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(schema.fields, className, schema.classLevelPermissions); mongoObject._id = className; - return this.setIndexesWithSchemaFormat(className, schema.indexes, {}, schema.fields) - .then(() => this._schemaCollection()) + return this._schemaCollection() .then(schemaCollection => schemaCollection._collection.insertOne(mongoObject)) .then(result => MongoSchemaCollection._TESTmongoSchemaToParseSchema(result.ops[0])) .catch(error => { @@ -428,7 +353,7 @@ export class MongoStorageAdapter { }, {}); readPreference = this._parseReadPreference(readPreference); - return this.createTextIndexesIfNeeded(className, query, schema) + return this.createTextIndexesIfNeeded(className, query) .then(() => this._adaptiveCollection(className)) .then(collection => collection.find(mongoWhere, { skip, @@ -538,11 +463,6 @@ export class MongoStorageAdapter { .then(collection => collection._mongoCollection.createIndex(index)); } - createIndexes(className, indexes) { - return this._adaptiveCollection(className) - .then(collection => collection._mongoCollection.createIndexes(indexes)); - } - createIndexesIfNeeded(className, fieldName, type) { if (type && type.type === 'Polygon') { const index = { @@ -553,26 +473,20 @@ export class MongoStorageAdapter { return Promise.resolve(); } - createTextIndexesIfNeeded(className, query, schema) { + createTextIndexesIfNeeded(className, query) { for(const fieldName in query) { if (!query[fieldName] || !query[fieldName].$text) { continue; } - const existingIndexes = schema.indexes; - for (const key in existingIndexes) { - const index = existingIndexes[key]; - if (index.hasOwnProperty(fieldName)) { - return Promise.resolve(); - } - } - const indexName = `${fieldName}_text`; - const textIndex = { - [indexName]: { [fieldName]: 'text' } + const index = { + [fieldName]: 'text' }; - return this.setIndexesWithSchemaFormat(className, textIndex, existingIndexes, schema.fields) + return this.createIndex(className, index) .catch((error) => { - if (error.code === 85) { // Index exist with different options - return this.setIndexesFromMongo(className); + if (error.code === 85) { + throw new Parse.Error( + Parse.Error.INTERNAL_SERVER_ERROR, + 'Only one text index is supported, please delete all text indexes to use new field.'); } throw error; }); @@ -584,26 +498,6 @@ export class MongoStorageAdapter { return this._adaptiveCollection(className) .then(collection => collection._mongoCollection.indexes()); } - - dropIndex(className, index) { - return this._adaptiveCollection(className) - .then(collection => collection._mongoCollection.dropIndex(index)); - } - - dropAllIndexes(className) { - return this._adaptiveCollection(className) - .then(collection => collection._mongoCollection.dropIndexes()); - } - - updateSchemaWithIndexes() { - return this.getAllClasses() - .then((classes) => { - const promises = classes.map((schema) => { - return this.setIndexesFromMongo(schema.className); - }); - return Promise.all(promises); - }); - } } export default MongoStorageAdapter; diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index a21d3278ad..246d5a6011 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -98,15 +98,10 @@ const toParseSchema = (schema) => { if (schema.classLevelPermissions) { clps = {...emptyCLPS, ...schema.classLevelPermissions}; } - let indexes = {}; - if (schema.indexes) { - indexes = {...schema.indexes}; - } return { className: schema.className, fields: schema.fields, classLevelPermissions: clps, - indexes, }; } @@ -613,64 +608,12 @@ export class PostgresStorageAdapter { }); } - setIndexesWithSchemaFormat(className, submittedIndexes, existingIndexes = {}, fields, conn) { - conn = conn || this._client; - if (submittedIndexes === undefined) { - return Promise.resolve(); - } - if (Object.keys(existingIndexes).length === 0) { - existingIndexes = { _id_: { _id: 1} }; - } - const deletedIndexes = []; - const insertedIndexes = []; - Object.keys(submittedIndexes).forEach(name => { - const field = submittedIndexes[name]; - if (existingIndexes[name] && field.__op !== 'Delete') { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Index ${name} exists, cannot update.`); - } - if (!existingIndexes[name] && field.__op === 'Delete') { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Index ${name} does not exist, cannot delete.`); - } - if (field.__op === 'Delete') { - deletedIndexes.push(name); - delete existingIndexes[name]; - } else { - Object.keys(field).forEach(key => { - if (!fields.hasOwnProperty(key)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Field ${key} does not exist, cannot add index.`); - } - }); - existingIndexes[name] = field; - insertedIndexes.push({ - key: field, - name, - }); - } - }); - let insertPromise = Promise.resolve(); - if (insertedIndexes.length > 0) { - insertPromise = this.createIndexes(className, insertedIndexes, conn); - } - let deletePromise = Promise.resolve(); - if (deletedIndexes.length > 0) { - deletePromise = this.dropIndexes(className, deletedIndexes, conn); - } - return deletePromise - .then(() => insertPromise) - .then(() => this._ensureSchemaCollectionExists()) - .then(() => { - const values = [className, 'schema', 'indexes', JSON.stringify(existingIndexes)] - return conn.none(`UPDATE "_SCHEMA" SET $2:name = json_object_set_key($2:name, $3::text, $4::jsonb) WHERE "className"=$1 `, values); - }); - } - createClass(className, schema) { return this._client.tx(t => { const q1 = this.createTable(className, schema, t); const q2 = t.none('INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($, $, true)', { className, schema }); - const q3 = this.setIndexesWithSchemaFormat(className, schema.indexes, {}, schema.fields, t); - return t.batch([q1, q2, q3]); + return t.batch([q1, q2]); }) .then(() => { return toParseSchema(schema) @@ -1605,25 +1548,6 @@ export class PostgresStorageAdapter { console.error(error); }); } - - createIndexes(className, indexes, conn) { - return (conn || this._client).tx(t => t.batch(indexes.map(i => { - return t.none('CREATE INDEX $1:name ON $2:name ($3:name)', [i.name, className, i.key]); - }))); - } - - dropIndexes(className, indexes, conn) { - return (conn || this._client).tx(t => t.batch(indexes.map(i => t.none('DROP INDEX $1:name', i)))); - } - - getIndexes(className) { - const qs = 'SELECT * FROM pg_indexes WHERE tablename = ${className}'; - return this._client.any(qs, {className}); - } - - updateSchemaWithIndexes() { - return Promise.resolve(); - } } function convertPolygonToSQL(polygon) { diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index b3cd130ac4..a954cfb409 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -1029,11 +1029,9 @@ DatabaseController.prototype.performInitialization = function() { throw error; }); - const indexPromise = this.adapter.updateSchemaWithIndexes(); - // Create tables for volatile classes const adapterInit = this.adapter.performInitialization({ VolatileClassesSchemas: SchemaController.VolatileClassesSchemas }); - return Promise.all([usernameUniqueness, emailUniqueness, roleUniqueness, adapterInit, indexPromise]); + return Promise.all([usernameUniqueness, emailUniqueness, roleUniqueness, adapterInit]); } function joinTableName(className, key) { diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index ab579b6aa2..88de10d4c3 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -287,28 +287,18 @@ const convertAdapterSchemaToParseSchema = ({...schema}) => { schema.fields.password = { type: 'String' }; } - if (schema.indexes && Object.keys(schema.indexes).length === 0) { - delete schema.indexes; - } - return schema; } -const injectDefaultSchema = ({className, fields, classLevelPermissions, indexes}) => { - const defaultSchema = { - className, - fields: { - ...defaultColumns._Default, - ...(defaultColumns[className] || {}), - ...fields, - }, - classLevelPermissions, - }; - if (indexes && Object.keys(indexes).length !== 0) { - defaultSchema.indexes = indexes; - } - return defaultSchema; -}; +const injectDefaultSchema = ({className, fields, classLevelPermissions}) => ({ + className, + fields: { + ...defaultColumns._Default, + ...(defaultColumns[className] || {}), + ...fields, + }, + classLevelPermissions, +}); const _HooksSchema = {className: "_Hooks", fields: defaultColumns._Hooks}; const _GlobalConfigSchema = { className: "_GlobalConfig", fields: defaultColumns._GlobalConfig } @@ -354,7 +344,6 @@ export default class SchemaController { _dbAdapter; data; perms; - indexes; constructor(databaseAdapter, schemaCache) { this._dbAdapter = databaseAdapter; @@ -363,8 +352,6 @@ export default class SchemaController { this.data = {}; // this.perms[className][operation] tells you the acl-style permissions this.perms = {}; - // this.indexes[className][operation] tells you the indexes - this.indexes = {}; } reloadData(options = {clearCache: false}) { @@ -383,11 +370,9 @@ export default class SchemaController { .then(allSchemas => { const data = {}; const perms = {}; - const indexes = {}; allSchemas.forEach(schema => { data[schema.className] = injectDefaultSchema(schema).fields; perms[schema.className] = schema.classLevelPermissions; - indexes[schema.className] = schema.indexes; }); // Inject the in-memory classes @@ -395,16 +380,13 @@ export default class SchemaController { const schema = injectDefaultSchema({ className }); data[className] = schema.fields; perms[className] = schema.classLevelPermissions; - indexes[className] = schema.indexes; }); this.data = data; this.perms = perms; - this.indexes = indexes; delete this.reloadDataPromise; }, (err) => { this.data = {}; this.perms = {}; - this.indexes = {}; delete this.reloadDataPromise; throw err; }); @@ -442,8 +424,7 @@ export default class SchemaController { return Promise.resolve({ className, fields: this.data[className], - classLevelPermissions: this.perms[className], - indexes: this.indexes[className] + classLevelPermissions: this.perms[className] }); } return this._cache.getOneSchema(className).then((cached) => { @@ -468,13 +449,13 @@ export default class SchemaController { // on success, and rejects with an error on fail. Ensure you // have authorization (master key, or client class creation // enabled) before calling this function. - addClassIfNotExists(className, fields = {}, classLevelPermissions, indexes = {}) { + addClassIfNotExists(className, fields = {}, classLevelPermissions) { var validationError = this.validateNewClass(className, fields, classLevelPermissions); if (validationError) { return Promise.reject(validationError); } - return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields, classLevelPermissions, indexes, className })) + return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields, classLevelPermissions, className })) .then(convertAdapterSchemaToParseSchema) .then((res) => { return this._cache.clear().then(() => { @@ -490,7 +471,7 @@ export default class SchemaController { }); } - updateClass(className, submittedFields, classLevelPermissions, indexes, database) { + updateClass(className, submittedFields, classLevelPermissions, database) { return this.getOneSchema(className) .then(schema => { const existingFields = schema.fields; @@ -528,6 +509,7 @@ export default class SchemaController { if (deletedFields.length > 0) { deletePromise = this.deleteFields(deletedFields, className, database); } + return deletePromise // Delete Everything .then(() => this.reloadData({ clearCache: true })) // Reload our Schema, so we have all the new values .then(() => { @@ -538,20 +520,12 @@ export default class SchemaController { return Promise.all(promises); }) .then(() => this.setPermissions(className, classLevelPermissions, newSchema)) - .then(() => this._dbAdapter.setIndexesWithSchemaFormat(className, indexes, schema.indexes, newSchema)) - .then(() => this.reloadData({ clearCache: true })) //TODO: Move this logic into the database adapter - .then(() => { - const reloadedSchema = { - className: className, - fields: this.data[className], - classLevelPermissions: this.perms[className], - }; - if (this.indexes[className] && Object.keys(this.indexes[className]).length !== 0) { - reloadedSchema.indexes = this.indexes[className]; - } - return reloadedSchema; - }); + .then(() => ({ + className: className, + fields: this.data[className], + classLevelPermissions: this.perms[className] + })); }) .catch(error => { if (error === undefined) { @@ -646,7 +620,8 @@ export default class SchemaController { return Promise.resolve(); } validateCLP(perms, newSchema); - return this._dbAdapter.setClassLevelPermissions(className, perms); + return this._dbAdapter.setClassLevelPermissions(className, perms) + .then(() => this.reloadData({ clearCache: true })); } // Returns a promise that resolves successfully to the new schema diff --git a/src/Routers/SchemasRouter.js b/src/Routers/SchemasRouter.js index f21c14c217..0e572c88a5 100644 --- a/src/Routers/SchemasRouter.js +++ b/src/Routers/SchemasRouter.js @@ -49,7 +49,7 @@ function createSchema(req) { } return req.config.database.loadSchema({ clearCache: true}) - .then(schema => schema.addClassIfNotExists(className, req.body.fields, req.body.classLevelPermissions, req.body.indexes)) + .then(schema => schema.addClassIfNotExists(className, req.body.fields, req.body.classLevelPermissions)) .then(schema => ({ response: schema })); } @@ -65,7 +65,7 @@ function modifySchema(req) { const className = req.params.className; return req.config.database.loadSchema({ clearCache: true}) - .then(schema => schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.body.indexes, req.config.database)) + .then(schema => schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database)) .then(result => ({response: result})); }