Use Prettier JS (#5017)

* Adds prettier

* Run lint before tests
This commit is contained in:
Florent Vilmart
2018-09-01 13:58:06 -04:00
committed by GitHub
parent 189cd259ee
commit d83a0b6808
240 changed files with 41098 additions and 29020 deletions

View File

@@ -2,9 +2,9 @@ const mongodb = require('mongodb');
const Collection = mongodb.Collection;
export default class MongoCollection {
_mongoCollection:Collection;
_mongoCollection: Collection;
constructor(mongoCollection:Collection) {
constructor(mongoCollection: Collection) {
this._mongoCollection = mongoCollection;
}
@@ -15,33 +15,58 @@ export default class MongoCollection {
// idea. Or even if this behavior is a good idea.
find(query, { skip, limit, sort, keys, maxTimeMS, readPreference } = {}) {
// Support for Full Text Search - $text
if(keys && keys.$score) {
if (keys && keys.$score) {
delete keys.$score;
keys.score = {$meta: 'textScore'};
keys.score = { $meta: 'textScore' };
}
return this._rawFind(query, { skip, limit, sort, keys, maxTimeMS, readPreference })
.catch(error => {
// Check for "no geoindex" error
if (error.code != 17007 && !error.message.match(/unable to find index for .geoNear/)) {
throw error;
}
// Figure out what key needs an index
const key = error.message.match(/field=([A-Za-z_0-9]+) /)[1];
if (!key) {
throw error;
}
return this._rawFind(query, {
skip,
limit,
sort,
keys,
maxTimeMS,
readPreference,
}).catch(error => {
// Check for "no geoindex" error
if (
error.code != 17007 &&
!error.message.match(/unable to find index for .geoNear/)
) {
throw error;
}
// Figure out what key needs an index
const key = error.message.match(/field=([A-Za-z_0-9]+) /)[1];
if (!key) {
throw error;
}
var index = {};
index[key] = '2d';
return this._mongoCollection.createIndex(index)
var index = {};
index[key] = '2d';
return (
this._mongoCollection
.createIndex(index)
// Retry, but just once.
.then(() => this._rawFind(query, { skip, limit, sort, keys, maxTimeMS, readPreference }));
});
.then(() =>
this._rawFind(query, {
skip,
limit,
sort,
keys,
maxTimeMS,
readPreference,
})
)
);
});
}
_rawFind(query, { skip, limit, sort, keys, maxTimeMS, readPreference } = {}) {
let findOperation = this._mongoCollection
.find(query, { skip, limit, sort, readPreference })
let findOperation = this._mongoCollection.find(query, {
skip,
limit,
sort,
readPreference,
});
if (keys) {
findOperation = findOperation.project(keys);
@@ -55,7 +80,13 @@ export default class MongoCollection {
}
count(query, { skip, limit, sort, maxTimeMS, readPreference } = {}) {
const countOperation = this._mongoCollection.count(query, { skip, limit, sort, maxTimeMS, readPreference });
const countOperation = this._mongoCollection.count(query, {
skip,
limit,
sort,
maxTimeMS,
readPreference,
});
return countOperation;
}
@@ -65,7 +96,9 @@ export default class MongoCollection {
}
aggregate(pipeline, { maxTimeMS, readPreference } = {}) {
return this._mongoCollection.aggregate(pipeline, { maxTimeMS, readPreference }).toArray();
return this._mongoCollection
.aggregate(pipeline, { maxTimeMS, readPreference })
.toArray();
}
insertOne(object) {
@@ -76,7 +109,7 @@ export default class MongoCollection {
// If there is nothing that matches the query - does insert
// Postgres Note: `INSERT ... ON CONFLICT UPDATE` that is available since 9.5.
upsertOne(query, update) {
return this._mongoCollection.update(query, update, { upsert: true })
return this._mongoCollection.update(query, update, { upsert: true });
}
updateOne(query, update) {
@@ -93,13 +126,17 @@ export default class MongoCollection {
_ensureSparseUniqueIndexInBackground(indexRequest) {
return new Promise((resolve, reject) => {
this._mongoCollection.ensureIndex(indexRequest, { unique: true, background: true, sparse: true }, (error) => {
if (error) {
reject(error);
} else {
resolve();
this._mongoCollection.ensureIndex(
indexRequest,
{ unique: true, background: true, sparse: true },
error => {
if (error) {
reject(error);
} else {
resolve();
}
}
});
);
});
}

View File

@@ -1,5 +1,5 @@
import MongoCollection from './MongoCollection';
import Parse from 'parse/node';
import Parse from 'parse/node';
function mongoFieldToParseSchemaField(type) {
if (type[0] === '*') {
@@ -15,31 +15,43 @@ function mongoFieldToParseSchemaField(type) {
};
}
switch (type) {
case 'number': return {type: 'Number'};
case 'string': return {type: 'String'};
case 'boolean': return {type: 'Boolean'};
case 'date': return {type: 'Date'};
case 'map':
case 'object': return {type: 'Object'};
case 'array': return {type: 'Array'};
case 'geopoint': return {type: 'GeoPoint'};
case 'file': return {type: 'File'};
case 'bytes': return {type: 'Bytes'};
case 'polygon': return {type: 'Polygon'};
case 'number':
return { type: 'Number' };
case 'string':
return { type: 'String' };
case 'boolean':
return { type: 'Boolean' };
case 'date':
return { type: 'Date' };
case 'map':
case 'object':
return { type: 'Object' };
case 'array':
return { type: 'Array' };
case 'geopoint':
return { type: 'GeoPoint' };
case 'file':
return { type: 'File' };
case 'bytes':
return { type: 'Bytes' };
case 'polygon':
return { type: 'Polygon' };
}
}
const nonFieldSchemaKeys = ['_id', '_metadata', '_client_permissions'];
function mongoSchemaFieldsToParseSchemaFields(schema) {
var fieldNames = Object.keys(schema).filter(key => nonFieldSchemaKeys.indexOf(key) === -1);
var fieldNames = Object.keys(schema).filter(
key => nonFieldSchemaKeys.indexOf(key) === -1
);
var response = fieldNames.reduce((obj, fieldName) => {
obj[fieldName] = mongoFieldToParseSchemaField(schema[fieldName])
obj[fieldName] = mongoFieldToParseSchemaField(schema[fieldName]);
return obj;
}, {});
response.ACL = {type: 'ACL'};
response.createdAt = {type: 'Date'};
response.updatedAt = {type: 'Date'};
response.objectId = {type: 'String'};
response.ACL = { type: 'ACL' };
response.createdAt = { type: 'Date' };
response.updatedAt = { type: 'Date' };
response.objectId = { type: 'String' };
return response;
}
@@ -53,23 +65,23 @@ const emptyCLPS = Object.freeze({
});
const defaultCLPS = Object.freeze({
find: {'*': true},
get: {'*': true},
create: {'*': true},
update: {'*': true},
delete: {'*': true},
addField: {'*': true},
find: { '*': true },
get: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
function mongoSchemaToParseSchema(mongoSchema) {
let clps = defaultCLPS;
let indexes = {}
let indexes = {};
if (mongoSchema._metadata) {
if (mongoSchema._metadata.class_permissions) {
clps = {...emptyCLPS, ...mongoSchema._metadata.class_permissions};
clps = { ...emptyCLPS, ...mongoSchema._metadata.class_permissions };
}
if (mongoSchema._metadata.indexes) {
indexes = {...mongoSchema._metadata.indexes};
indexes = { ...mongoSchema._metadata.indexes };
}
}
return {
@@ -90,23 +102,34 @@ function _mongoSchemaQueryFromNameQuery(name: string, query) {
return object;
}
// Returns a type suitable for inserting into mongo _SCHEMA collection.
// Does no validation. That is expected to be done in Parse Server.
function parseFieldTypeToMongoFieldType({ type, targetClass }) {
switch (type) {
case 'Pointer': return `*${targetClass}`;
case 'Relation': return `relation<${targetClass}>`;
case 'Number': return 'number';
case 'String': return 'string';
case 'Boolean': return 'boolean';
case 'Date': return 'date';
case 'Object': return 'object';
case 'Array': return 'array';
case 'GeoPoint': return 'geopoint';
case 'File': return 'file';
case 'Bytes': return 'bytes';
case 'Polygon': return 'polygon';
case 'Pointer':
return `*${targetClass}`;
case 'Relation':
return `relation<${targetClass}>`;
case 'Number':
return 'number';
case 'String':
return 'string';
case 'Boolean':
return 'boolean';
case 'Date':
return 'date';
case 'Object':
return 'object';
case 'Array':
return 'array';
case 'GeoPoint':
return 'geopoint';
case 'File':
return 'file';
case 'Bytes':
return 'bytes';
case 'Polygon':
return 'polygon';
}
}
@@ -118,43 +141,60 @@ class MongoSchemaCollection {
}
_fetchAllSchemasFrom_SCHEMA() {
return this._collection._rawFind({})
return this._collection
._rawFind({})
.then(schemas => schemas.map(mongoSchemaToParseSchema));
}
_fetchOneSchemaFrom_SCHEMA(name: string) {
return this._collection._rawFind(_mongoSchemaQueryFromNameQuery(name), { limit: 1 }).then(results => {
if (results.length === 1) {
return mongoSchemaToParseSchema(results[0]);
} else {
throw undefined;
}
});
return this._collection
._rawFind(_mongoSchemaQueryFromNameQuery(name), { limit: 1 })
.then(results => {
if (results.length === 1) {
return mongoSchemaToParseSchema(results[0]);
} else {
throw undefined;
}
});
}
// Atomically find and delete an object based on query.
findAndDeleteSchema(name: string) {
return this._collection._mongoCollection.findAndRemove(_mongoSchemaQueryFromNameQuery(name), []);
return this._collection._mongoCollection.findAndRemove(
_mongoSchemaQueryFromNameQuery(name),
[]
);
}
insertSchema(schema: any) {
return this._collection.insertOne(schema)
return this._collection
.insertOne(schema)
.then(result => mongoSchemaToParseSchema(result.ops[0]))
.catch(error => {
if (error.code === 11000) { //Mongo's duplicate key error
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Class already exists.');
if (error.code === 11000) {
//Mongo's duplicate key error
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'Class already exists.'
);
} else {
throw error;
}
})
});
}
updateSchema(name: string, update) {
return this._collection.updateOne(_mongoSchemaQueryFromNameQuery(name), update);
return this._collection.updateOne(
_mongoSchemaQueryFromNameQuery(name),
update
);
}
upsertSchema(name: string, query: string, update) {
return this._collection.upsertOne(_mongoSchemaQueryFromNameQuery(name, query), update);
return this._collection.upsertOne(
_mongoSchemaQueryFromNameQuery(name, query),
update
);
}
// Add a field to the schema. If database does not support the field
@@ -170,34 +210,45 @@ class MongoSchemaCollection {
// TODO: don't spend an extra query on finding the schema if the type we are trying to add isn't a GeoPoint.
addFieldIfNotExists(className: string, fieldName: string, type: string) {
return this._fetchOneSchemaFrom_SCHEMA(className)
.then(schema => {
// If a field with this name already exists, it will be handled elsewhere.
if (schema.fields[fieldName] != undefined) {
return;
}
// The schema exists. Check for existing GeoPoints.
if (type.type === 'GeoPoint') {
// Make sure there are not other geopoint fields
if (Object.keys(schema.fields).some(existingField => schema.fields[existingField].type === 'GeoPoint')) {
throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'MongoDB only supports one GeoPoint field in a class.');
.then(
schema => {
// If a field with this name already exists, it will be handled elsewhere.
if (schema.fields[fieldName] != undefined) {
return;
}
// The schema exists. Check for existing GeoPoints.
if (type.type === 'GeoPoint') {
// Make sure there are not other geopoint fields
if (
Object.keys(schema.fields).some(
existingField =>
schema.fields[existingField].type === 'GeoPoint'
)
) {
throw new Parse.Error(
Parse.Error.INCORRECT_TYPE,
'MongoDB only supports one GeoPoint field in a class.'
);
}
}
}
return;
}, error => {
// If error is undefined, the schema doesn't exist, and we can create the schema with the field.
// If some other error, reject with it.
if (error === undefined) {
return;
},
error => {
// If error is undefined, the schema doesn't exist, and we can create the schema with the field.
// If some other error, reject with it.
if (error === undefined) {
return;
}
throw error;
}
throw error;
})
)
.then(() => {
// We use $exists and $set to avoid overwriting the field type if it
// already exists. (it could have added inbetween the last query and the update)
// We use $exists and $set to avoid overwriting the field type if it
// already exists. (it could have added inbetween the last query and the update)
return this.upsertSchema(
className,
{ [fieldName]: { '$exists': false } },
{ '$set' : { [fieldName]: parseFieldTypeToMongoFieldType(type) } }
{ [fieldName]: { $exists: false } },
{ $set: { [fieldName]: parseFieldTypeToMongoFieldType(type) } }
);
});
}
@@ -205,7 +256,7 @@ class MongoSchemaCollection {
// Exported for testing reasons and because we haven't moved all mongo schema format
// related logic into the database adapter yet.
MongoSchemaCollection._TESTmongoSchemaToParseSchema = mongoSchemaToParseSchema
MongoSchemaCollection.parseFieldTypeToMongoFieldType = parseFieldTypeToMongoFieldType
MongoSchemaCollection._TESTmongoSchemaToParseSchema = mongoSchemaToParseSchema;
MongoSchemaCollection.parseFieldTypeToMongoFieldType = parseFieldTypeToMongoFieldType;
export default MongoSchemaCollection
export default MongoSchemaCollection;

View File

@@ -1,11 +1,13 @@
// @flow
import MongoCollection from './MongoCollection';
import MongoCollection from './MongoCollection';
import MongoSchemaCollection from './MongoSchemaCollection';
import { StorageAdapter } from '../StorageAdapter';
import type { SchemaType,
import { StorageAdapter } from '../StorageAdapter';
import type {
SchemaType,
QueryType,
StorageClass,
QueryOptions } from '../StorageAdapter';
QueryOptions,
} from '../StorageAdapter';
import {
parse as parseUrl,
format as formatUrl,
@@ -19,11 +21,11 @@ import {
transformPointerString,
} from './MongoTransform';
// @flow-disable-next
import Parse from 'parse/node';
import Parse from 'parse/node';
// @flow-disable-next
import _ from 'lodash';
import defaults from '../../../defaults';
import logger from '../../../logger';
import _ from 'lodash';
import defaults from '../../../defaults';
import logger from '../../../logger';
// @flow-disable-next
const mongodb = require('mongodb');
@@ -33,7 +35,8 @@ const ReadPreference = mongodb.ReadPreference;
const MongoSchemaCollectionName = '_SCHEMA';
const storageAdapterAllCollections = mongoAdapter => {
return mongoAdapter.connect()
return mongoAdapter
.connect()
.then(() => mongoAdapter.database.collections())
.then(collections => {
return collections.filter(collection => {
@@ -42,12 +45,14 @@ const storageAdapterAllCollections = mongoAdapter => {
}
// TODO: If you have one app with a collection prefix that happens to be a prefix of another
// apps prefix, this will go very very badly. We should fix that somehow.
return (collection.collectionName.indexOf(mongoAdapter._collectionPrefix) == 0);
return (
collection.collectionName.indexOf(mongoAdapter._collectionPrefix) == 0
);
});
});
}
};
const convertParseSchemaToMongoSchema = ({...schema}) => {
const convertParseSchemaToMongoSchema = ({ ...schema }) => {
delete schema.fields._rperm;
delete schema.fields._wperm;
@@ -60,11 +65,16 @@ const convertParseSchemaToMongoSchema = ({...schema}) => {
}
return 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,
indexes
) => {
const mongoObject = {
_id: className,
objectId: 'string',
@@ -74,7 +84,9 @@ const mongoSchemaFromFieldsAndClassNameAndCLP = (fields, className, classLevelPe
};
for (const fieldName in fields) {
mongoObject[fieldName] = MongoSchemaCollection.parseFieldTypeToMongoFieldType(fields[fieldName]);
mongoObject[
fieldName
] = MongoSchemaCollection.parseFieldTypeToMongoFieldType(fields[fieldName]);
}
if (typeof classLevelPermissions !== 'undefined') {
@@ -86,18 +98,22 @@ const mongoSchemaFromFieldsAndClassNameAndCLP = (fields, className, classLevelPe
}
}
if (indexes && typeof indexes === 'object' && Object.keys(indexes).length > 0) {
if (
indexes &&
typeof indexes === 'object' &&
Object.keys(indexes).length > 0
) {
mongoObject._metadata = mongoObject._metadata || {};
mongoObject._metadata.indexes = indexes;
}
if (!mongoObject._metadata) { // cleanup the unused _metadata
if (!mongoObject._metadata) {
// cleanup the unused _metadata
delete mongoObject._metadata;
}
return mongoObject;
}
};
export class MongoStorageAdapter implements StorageAdapter {
// Private
@@ -135,34 +151,40 @@ export class MongoStorageAdapter implements StorageAdapter {
// encoded
const encodedUri = formatUrl(parseUrl(this._uri));
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions).then(client => {
// Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client
// Fortunately, we can get back the options and use them to select the proper DB.
// https://github.com/mongodb/node-mongodb-native/blob/2c35d76f08574225b8db02d7bef687123e6bb018/lib/mongo_client.js#L885
const options = client.s.options;
const database = client.db(options.dbName);
if (!database) {
delete this.connectionPromise;
return;
}
database.on('error', () => {
this.connectionPromise = MongoClient.connect(
encodedUri,
this._mongoOptions
)
.then(client => {
// Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client
// Fortunately, we can get back the options and use them to select the proper DB.
// https://github.com/mongodb/node-mongodb-native/blob/2c35d76f08574225b8db02d7bef687123e6bb018/lib/mongo_client.js#L885
const options = client.s.options;
const database = client.db(options.dbName);
if (!database) {
delete this.connectionPromise;
return;
}
database.on('error', () => {
delete this.connectionPromise;
});
database.on('close', () => {
delete this.connectionPromise;
});
this.client = client;
this.database = database;
})
.catch(err => {
delete this.connectionPromise;
return Promise.reject(err);
});
database.on('close', () => {
delete this.connectionPromise;
});
this.client = client;
this.database = database;
}).catch((err) => {
delete this.connectionPromise;
return Promise.reject(err);
});
return this.connectionPromise;
}
handleError<T>(error: ?(Error | Parse.Error)): Promise<T> {
if (error && error.code === 13) { // Unauthorized error
if (error && error.code === 13) {
// Unauthorized error
delete this.client;
delete this.database;
delete this.connectionPromise;
@@ -192,36 +214,55 @@ export class MongoStorageAdapter implements StorageAdapter {
}
classExists(name: string) {
return this.connect().then(() => {
return this.database.listCollections({ name: this._collectionPrefix + name }).toArray();
}).then(collections => {
return collections.length > 0;
}).catch(err => this.handleError(err));
return this.connect()
.then(() => {
return this.database
.listCollections({ name: this._collectionPrefix + name })
.toArray();
})
.then(collections => {
return collections.length > 0;
})
.catch(err => this.handleError(err));
}
setClassLevelPermissions(className: string, CLPs: any): Promise<void> {
return this._schemaCollection()
.then(schemaCollection => schemaCollection.updateSchema(className, {
$set: { '_metadata.class_permissions': CLPs }
})).catch(err => this.handleError(err));
.then(schemaCollection =>
schemaCollection.updateSchema(className, {
$set: { '_metadata.class_permissions': CLPs },
})
)
.catch(err => this.handleError(err));
}
setIndexesWithSchemaFormat(className: string, submittedIndexes: any, existingIndexes: any = {}, fields: any): Promise<void> {
setIndexesWithSchemaFormat(
className: string,
submittedIndexes: any,
existingIndexes: any = {},
fields: any
): Promise<void> {
if (submittedIndexes === undefined) {
return Promise.resolve();
}
if (Object.keys(existingIndexes).length === 0) {
existingIndexes = { _id_: { _id: 1} };
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.`);
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.`);
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);
@@ -230,7 +271,10 @@ export class MongoStorageAdapter implements StorageAdapter {
} 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.`);
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
`Field ${key} does not exist, cannot add index.`
);
}
});
existingIndexes[name] = field;
@@ -247,30 +291,34 @@ export class MongoStorageAdapter implements StorageAdapter {
return Promise.all(deletePromises)
.then(() => insertPromise)
.then(() => this._schemaCollection())
.then(schemaCollection => schemaCollection.updateSchema(className, {
$set: { '_metadata.indexes': existingIndexes }
}))
.then(schemaCollection =>
schemaCollection.updateSchema(className, {
$set: { '_metadata.indexes': existingIndexes },
})
)
.catch(err => this.handleError(err));
}
setIndexesFromMongo(className: string) {
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';
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 }
}));
})
obj[index.name] = index.key;
return obj;
}, {});
return this._schemaCollection().then(schemaCollection =>
schemaCollection.updateSchema(className, {
$set: { '_metadata.indexes': indexes },
})
);
})
.catch(err => this.handleError(err))
.catch(() => {
// Ignore if collection not found
@@ -280,17 +328,33 @@ export class MongoStorageAdapter implements StorageAdapter {
createClass(className: string, schema: SchemaType): Promise<void> {
schema = convertParseSchemaToMongoSchema(schema);
const mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(schema.fields, className, schema.classLevelPermissions, schema.indexes);
const mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(
schema.fields,
className,
schema.classLevelPermissions,
schema.indexes
);
mongoObject._id = className;
return this.setIndexesWithSchemaFormat(className, schema.indexes, {}, schema.fields)
return this.setIndexesWithSchemaFormat(
className,
schema.indexes,
{},
schema.fields
)
.then(() => this._schemaCollection())
.then(schemaCollection => schemaCollection.insertSchema(mongoObject))
.catch(err => this.handleError(err));
}
addFieldIfNotExists(className: string, fieldName: string, type: any): Promise<void> {
addFieldIfNotExists(
className: string,
fieldName: string,
type: any
): Promise<void> {
return this._schemaCollection()
.then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type))
.then(schemaCollection =>
schemaCollection.addFieldIfNotExists(className, fieldName, type)
)
.then(() => this.createIndexesIfNeeded(className, fieldName, type))
.catch(err => this.handleError(err));
}
@@ -298,24 +362,33 @@ export class MongoStorageAdapter implements StorageAdapter {
// Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.)
// and resolves with false if it wasn't (eg. a join table). Rejects if deletion was impossible.
deleteClass(className: string) {
return this._adaptiveCollection(className)
.then(collection => collection.drop())
.catch(error => {
// 'ns not found' means collection was already gone. Ignore deletion attempt.
if (error.message == 'ns not found') {
return;
}
throw error;
})
// We've dropped the collection, now remove the _SCHEMA document
.then(() => this._schemaCollection())
.then(schemaCollection => schemaCollection.findAndDeleteSchema(className))
.catch(err => this.handleError(err));
return (
this._adaptiveCollection(className)
.then(collection => collection.drop())
.catch(error => {
// 'ns not found' means collection was already gone. Ignore deletion attempt.
if (error.message == 'ns not found') {
return;
}
throw error;
})
// We've dropped the collection, now remove the _SCHEMA document
.then(() => this._schemaCollection())
.then(schemaCollection =>
schemaCollection.findAndDeleteSchema(className)
)
.catch(err => this.handleError(err))
);
}
deleteAllClasses(fast: boolean) {
return storageAdapterAllCollections(this)
.then(collections => Promise.all(collections.map(collection => fast ? collection.remove({}) : collection.drop())));
return storageAdapterAllCollections(this).then(collections =>
Promise.all(
collections.map(
collection => (fast ? collection.remove({}) : collection.drop())
)
)
);
}
// Remove the column and all the data. For Relations, the _Join collection is handled
@@ -341,17 +414,17 @@ export class MongoStorageAdapter implements StorageAdapter {
deleteFields(className: string, schema: SchemaType, fieldNames: string[]) {
const mongoFormatNames = fieldNames.map(fieldName => {
if (schema.fields[fieldName].type === 'Pointer') {
return `_p_${fieldName}`
return `_p_${fieldName}`;
} else {
return fieldName;
}
});
const collectionUpdate = { '$unset' : {} };
const collectionUpdate = { $unset: {} };
mongoFormatNames.forEach(name => {
collectionUpdate['$unset'][name] = null;
});
const schemaUpdate = { '$unset' : {} };
const schemaUpdate = { $unset: {} };
fieldNames.forEach(name => {
schemaUpdate['$unset'][name] = null;
});
@@ -359,7 +432,9 @@ export class MongoStorageAdapter implements StorageAdapter {
return this._adaptiveCollection(className)
.then(collection => collection.updateMany({}, collectionUpdate))
.then(() => this._schemaCollection())
.then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate))
.then(schemaCollection =>
schemaCollection.updateSchema(className, schemaUpdate)
)
.catch(err => this.handleError(err));
}
@@ -367,7 +442,10 @@ export class MongoStorageAdapter implements StorageAdapter {
// schemas cannot be retrieved, returns a promise that rejects. Requirements for the
// rejection reason are TBD.
getAllClasses(): Promise<StorageClass[]> {
return this._schemaCollection().then(schemasCollection => schemasCollection._fetchAllSchemasFrom_SCHEMA())
return this._schemaCollection()
.then(schemasCollection =>
schemasCollection._fetchAllSchemasFrom_SCHEMA()
)
.catch(err => this.handleError(err));
}
@@ -376,7 +454,9 @@ export class MongoStorageAdapter implements StorageAdapter {
// undefined as the reason.
getClass(className: string): Promise<StorageClass> {
return this._schemaCollection()
.then(schemasCollection => schemasCollection._fetchOneSchemaFrom_SCHEMA(className))
.then(schemasCollection =>
schemasCollection._fetchOneSchemaFrom_SCHEMA(className)
)
.catch(err => this.handleError(err));
}
@@ -385,15 +465,25 @@ export class MongoStorageAdapter implements StorageAdapter {
// the schema only for the legacy mongo format. We'll figure that out later.
createObject(className: string, schema: SchemaType, object: any) {
schema = convertParseSchemaToMongoSchema(schema);
const mongoObject = parseObjectToMongoObjectForCreate(className, object, schema);
const mongoObject = parseObjectToMongoObjectForCreate(
className,
object,
schema
);
return this._adaptiveCollection(className)
.then(collection => collection.insertOne(mongoObject))
.catch(error => {
if (error.code === 11000) { // Duplicate value
const err = new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
if (error.code === 11000) {
// Duplicate value
const err = new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'A duplicate value for a field with unique values was provided'
);
err.underlyingError = error;
if (error.message) {
const matches = error.message.match(/index:[\sa-zA-Z0-9_\-\.]+\$?([a-zA-Z_-]+)_1/);
const matches = error.message.match(
/index:[\sa-zA-Z0-9_\-\.]+\$?([a-zA-Z_-]+)_1/
);
if (matches && Array.isArray(matches)) {
err.userInfo = { duplicated_field: matches[1] };
}
@@ -408,26 +498,44 @@ export class MongoStorageAdapter implements StorageAdapter {
// Remove all objects that match the given Parse Query.
// If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined.
// If there is some other error, reject with INTERNAL_SERVER_ERROR.
deleteObjectsByQuery(className: string, schema: SchemaType, query: QueryType) {
deleteObjectsByQuery(
className: string,
schema: SchemaType,
query: QueryType
) {
schema = convertParseSchemaToMongoSchema(schema);
return this._adaptiveCollection(className)
.then(collection => {
const mongoWhere = transformWhere(className, query, schema);
return collection.deleteMany(mongoWhere)
return collection.deleteMany(mongoWhere);
})
.catch(err => this.handleError(err))
.then(({ result }) => {
if (result.n === 0) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
.then(
({ result }) => {
if (result.n === 0) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Object not found.'
);
}
return Promise.resolve();
},
() => {
throw new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'Database adapter error'
);
}
return Promise.resolve();
}, () => {
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error');
});
);
}
// Apply the update to all objects that match the given Parse Query.
updateObjectsByQuery(className: string, schema: SchemaType, query: QueryType, update: any) {
updateObjectsByQuery(
className: string,
schema: SchemaType,
query: QueryType,
update: any
) {
schema = convertParseSchemaToMongoSchema(schema);
const mongoUpdate = transformUpdate(className, update, schema);
const mongoWhere = transformWhere(className, query, schema);
@@ -438,16 +546,28 @@ export class MongoStorageAdapter implements StorageAdapter {
// Atomically finds and updates an object based on query.
// Return value not currently well specified.
findOneAndUpdate(className: string, schema: SchemaType, query: QueryType, update: any) {
findOneAndUpdate(
className: string,
schema: SchemaType,
query: QueryType,
update: any
) {
schema = convertParseSchemaToMongoSchema(schema);
const mongoUpdate = transformUpdate(className, update, schema);
const mongoWhere = transformWhere(className, query, schema);
return this._adaptiveCollection(className)
.then(collection => collection._mongoCollection.findAndModify(mongoWhere, [], mongoUpdate, { new: true }))
.then(collection =>
collection._mongoCollection.findAndModify(mongoWhere, [], mongoUpdate, {
new: true,
})
)
.then(result => mongoObjectToParseObject(className, result.value, schema))
.catch(error => {
if (error.code === 11000) {
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'A duplicate value for a field with unique values was provided'
);
}
throw error;
})
@@ -455,7 +575,12 @@ export class MongoStorageAdapter implements StorageAdapter {
}
// Hopefully we can get rid of this. It's only used for config and hooks.
upsertOneObject(className: string, schema: SchemaType, query: QueryType, update: any) {
upsertOneObject(
className: string,
schema: SchemaType,
query: QueryType,
update: any
) {
schema = convertParseSchemaToMongoSchema(schema);
const mongoUpdate = transformUpdate(className, update, schema);
const mongoWhere = transformWhere(className, query, schema);
@@ -465,32 +590,49 @@ export class MongoStorageAdapter implements StorageAdapter {
}
// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
find(className: string, schema: SchemaType, query: QueryType, { skip, limit, sort, keys, readPreference }: QueryOptions): Promise<any> {
find(
className: string,
schema: SchemaType,
query: QueryType,
{ skip, limit, sort, keys, readPreference }: QueryOptions
): Promise<any> {
schema = convertParseSchemaToMongoSchema(schema);
const mongoWhere = transformWhere(className, query, schema);
const mongoSort = _.mapKeys(sort, (value, fieldName) => transformKey(className, fieldName, schema));
const mongoKeys = _.reduce(keys, (memo, key) => {
if (key === 'ACL') {
memo['_rperm'] = 1;
memo['_wperm'] = 1;
} else {
memo[transformKey(className, key, schema)] = 1;
}
return memo;
}, {});
const mongoSort = _.mapKeys(sort, (value, fieldName) =>
transformKey(className, fieldName, schema)
);
const mongoKeys = _.reduce(
keys,
(memo, key) => {
if (key === 'ACL') {
memo['_rperm'] = 1;
memo['_wperm'] = 1;
} else {
memo[transformKey(className, key, schema)] = 1;
}
return memo;
},
{}
);
readPreference = this._parseReadPreference(readPreference);
return this.createTextIndexesIfNeeded(className, query, schema)
.then(() => this._adaptiveCollection(className))
.then(collection => collection.find(mongoWhere, {
skip,
limit,
sort: mongoSort,
keys: mongoKeys,
maxTimeMS: this._maxTimeMS,
readPreference,
}))
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)))
.then(collection =>
collection.find(mongoWhere, {
skip,
limit,
sort: mongoSort,
keys: mongoKeys,
maxTimeMS: this._maxTimeMS,
readPreference,
})
)
.then(objects =>
objects.map(object =>
mongoObjectToParseObject(className, object, schema)
)
)
.catch(err => this.handleError(err));
}
@@ -499,18 +641,29 @@ export class MongoStorageAdapter implements StorageAdapter {
// As such, we shouldn't expose this function to users of parse until we have an out-of-band
// Way of determining if a field is nullable. Undefined doesn't count against uniqueness,
// which is why we use sparse indexes.
ensureUniqueness(className: string, schema: SchemaType, fieldNames: string[]) {
ensureUniqueness(
className: string,
schema: SchemaType,
fieldNames: string[]
) {
schema = convertParseSchemaToMongoSchema(schema);
const indexCreationRequest = {};
const mongoFieldNames = fieldNames.map(fieldName => transformKey(className, fieldName, schema));
const mongoFieldNames = fieldNames.map(fieldName =>
transformKey(className, fieldName, schema)
);
mongoFieldNames.forEach(fieldName => {
indexCreationRequest[fieldName] = 1;
});
return this._adaptiveCollection(className)
.then(collection => collection._ensureSparseUniqueIndexInBackground(indexCreationRequest))
.then(collection =>
collection._ensureSparseUniqueIndexInBackground(indexCreationRequest)
)
.catch(error => {
if (error.code === 11000) {
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Tried to ensure field uniqueness for a class that already has duplicates.');
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'Tried to ensure field uniqueness for a class that already has duplicates.'
);
}
throw error;
})
@@ -519,33 +672,52 @@ export class MongoStorageAdapter implements StorageAdapter {
// Used in tests
_rawFind(className: string, query: QueryType) {
return this._adaptiveCollection(className).then(collection => collection.find(query, {
maxTimeMS: this._maxTimeMS,
})).catch(err => this.handleError(err));
}
// Executes a count.
count(className: string, schema: SchemaType, query: QueryType, readPreference: ?string) {
schema = convertParseSchemaToMongoSchema(schema);
readPreference = this._parseReadPreference(readPreference);
return this._adaptiveCollection(className)
.then(collection => collection.count(transformWhere(className, query, schema), {
maxTimeMS: this._maxTimeMS,
readPreference,
}))
.then(collection =>
collection.find(query, {
maxTimeMS: this._maxTimeMS,
})
)
.catch(err => this.handleError(err));
}
distinct(className: string, schema: SchemaType, query: QueryType, fieldName: string) {
// Executes a count.
count(
className: string,
schema: SchemaType,
query: QueryType,
readPreference: ?string
) {
schema = convertParseSchemaToMongoSchema(schema);
const isPointerField = schema.fields[fieldName] && schema.fields[fieldName].type === 'Pointer';
readPreference = this._parseReadPreference(readPreference);
return this._adaptiveCollection(className)
.then(collection =>
collection.count(transformWhere(className, query, schema), {
maxTimeMS: this._maxTimeMS,
readPreference,
})
)
.catch(err => this.handleError(err));
}
distinct(
className: string,
schema: SchemaType,
query: QueryType,
fieldName: string
) {
schema = convertParseSchemaToMongoSchema(schema);
const isPointerField =
schema.fields[fieldName] && schema.fields[fieldName].type === 'Pointer';
if (isPointerField) {
fieldName = `_p_${fieldName}`
fieldName = `_p_${fieldName}`;
}
return this._adaptiveCollection(className)
.then(collection => collection.distinct(fieldName, transformWhere(className, query, schema)))
.then(collection =>
collection.distinct(fieldName, transformWhere(className, query, schema))
)
.then(objects => {
objects = objects.filter((obj) => obj != null);
objects = objects.filter(obj => obj != null);
return objects.map(object => {
if (isPointerField) {
const field = fieldName.substring(3);
@@ -557,12 +729,21 @@ export class MongoStorageAdapter implements StorageAdapter {
.catch(err => this.handleError(err));
}
aggregate(className: string, schema: any, pipeline: any, readPreference: ?string) {
aggregate(
className: string,
schema: any,
pipeline: any,
readPreference: ?string
) {
let isPointerField = false;
pipeline = pipeline.map((stage) => {
pipeline = pipeline.map(stage => {
if (stage.$group) {
stage.$group = this._parseAggregateGroupArgs(schema, stage.$group);
if (stage.$group._id && (typeof stage.$group._id === 'string') && stage.$group._id.indexOf('$_p_') >= 0) {
if (
stage.$group._id &&
typeof stage.$group._id === 'string' &&
stage.$group._id.indexOf('$_p_') >= 0
) {
isPointerField = true;
}
}
@@ -570,13 +751,21 @@ export class MongoStorageAdapter implements StorageAdapter {
stage.$match = this._parseAggregateArgs(schema, stage.$match);
}
if (stage.$project) {
stage.$project = this._parseAggregateProjectArgs(schema, stage.$project);
stage.$project = this._parseAggregateProjectArgs(
schema,
stage.$project
);
}
return stage;
});
readPreference = this._parseReadPreference(readPreference);
return this._adaptiveCollection(className)
.then(collection => collection.aggregate(pipeline, { readPreference, maxTimeMS: this._maxTimeMS }))
.then(collection =>
collection.aggregate(pipeline, {
readPreference,
maxTimeMS: this._maxTimeMS,
})
)
.catch(error => {
if (error.code === 16006) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, error.message);
@@ -598,7 +787,11 @@ export class MongoStorageAdapter implements StorageAdapter {
});
return results;
})
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)))
.then(objects =>
objects.map(object =>
mongoObjectToParseObject(className, object, schema)
)
)
.catch(err => this.handleError(err));
}
@@ -623,7 +816,7 @@ export class MongoStorageAdapter implements StorageAdapter {
// down a tree to find a "leaf node" and checking to see if it needs to be converted.
_parseAggregateArgs(schema: any, pipeline: any): any {
if (Array.isArray(pipeline)) {
return pipeline.map((value) => this._parseAggregateArgs(schema, value));
return pipeline.map(value => this._parseAggregateArgs(schema, value));
} else if (typeof pipeline === 'object') {
const returnValue = {};
for (const field in pipeline) {
@@ -632,12 +825,20 @@ export class MongoStorageAdapter implements StorageAdapter {
// Pass objects down to MongoDB...this is more than likely an $exists operator.
returnValue[`_p_${field}`] = pipeline[field];
} else {
returnValue[`_p_${field}`] = `${schema.fields[field].targetClass}$${pipeline[field]}`;
returnValue[`_p_${field}`] = `${schema.fields[field].targetClass}$${
pipeline[field]
}`;
}
} else if (schema.fields[field] && schema.fields[field].type === 'Date') {
} else if (
schema.fields[field] &&
schema.fields[field].type === 'Date'
) {
returnValue[field] = this._convertToDate(pipeline[field]);
} else {
returnValue[field] = this._parseAggregateArgs(schema, pipeline[field]);
returnValue[field] = this._parseAggregateArgs(
schema,
pipeline[field]
);
}
if (field === 'objectId') {
@@ -690,11 +891,16 @@ export class MongoStorageAdapter implements StorageAdapter {
// updatedAt or objectId and change it accordingly.
_parseAggregateGroupArgs(schema: any, pipeline: any): any {
if (Array.isArray(pipeline)) {
return pipeline.map((value) => this._parseAggregateGroupArgs(schema, value));
return pipeline.map(value =>
this._parseAggregateGroupArgs(schema, value)
);
} else if (typeof pipeline === 'object') {
const returnValue = {};
for (const field in pipeline) {
returnValue[field] = this._parseAggregateGroupArgs(schema, pipeline[field]);
returnValue[field] = this._parseAggregateGroupArgs(
schema,
pipeline[field]
);
}
return returnValue;
} else if (typeof pipeline === 'string') {
@@ -719,34 +925,37 @@ export class MongoStorageAdapter implements StorageAdapter {
return new Date(value);
}
const returnValue = {}
const returnValue = {};
for (const field in value) {
returnValue[field] = this._convertToDate(value[field])
returnValue[field] = this._convertToDate(value[field]);
}
return returnValue;
}
_parseReadPreference(readPreference: ?string): ?string {
switch (readPreference) {
case 'PRIMARY':
readPreference = ReadPreference.PRIMARY;
break;
case 'PRIMARY_PREFERRED':
readPreference = ReadPreference.PRIMARY_PREFERRED;
break;
case 'SECONDARY':
readPreference = ReadPreference.SECONDARY;
break;
case 'SECONDARY_PREFERRED':
readPreference = ReadPreference.SECONDARY_PREFERRED;
break;
case 'NEAREST':
readPreference = ReadPreference.NEAREST;
break;
case undefined:
break;
default:
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Not supported read preference.');
case 'PRIMARY':
readPreference = ReadPreference.PRIMARY;
break;
case 'PRIMARY_PREFERRED':
readPreference = ReadPreference.PRIMARY_PREFERRED;
break;
case 'SECONDARY':
readPreference = ReadPreference.SECONDARY;
break;
case 'SECONDARY_PREFERRED':
readPreference = ReadPreference.SECONDARY_PREFERRED;
break;
case 'NEAREST':
readPreference = ReadPreference.NEAREST;
break;
case undefined:
break;
default:
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'Not supported read preference.'
);
}
return readPreference;
}
@@ -770,15 +979,19 @@ export class MongoStorageAdapter implements StorageAdapter {
createIndexesIfNeeded(className: string, fieldName: string, type: any) {
if (type && type.type === 'Polygon') {
const index = {
[fieldName]: '2dsphere'
[fieldName]: '2dsphere',
};
return this.createIndex(className, index);
}
return Promise.resolve();
}
createTextIndexesIfNeeded(className: string, query: QueryType, schema: any): Promise<void> {
for(const fieldName in query) {
createTextIndexesIfNeeded(
className: string,
query: QueryType,
schema: any
): Promise<void> {
for (const fieldName in query) {
if (!query[fieldName] || !query[fieldName].$text) {
continue;
}
@@ -791,15 +1004,20 @@ export class MongoStorageAdapter implements StorageAdapter {
}
const indexName = `${fieldName}_text`;
const textIndex = {
[indexName]: { [fieldName]: 'text' }
[indexName]: { [fieldName]: 'text' },
};
return this.setIndexesWithSchemaFormat(className, textIndex, existingIndexes, schema.fields)
.catch((error) => {
if (error.code === 85) { // Index exist with different options
return this.setIndexesFromMongo(className);
}
throw error;
});
return this.setIndexesWithSchemaFormat(
className,
textIndex,
existingIndexes,
schema.fields
).catch(error => {
if (error.code === 85) {
// Index exist with different options
return this.setIndexesFromMongo(className);
}
throw error;
});
}
return Promise.resolve();
}
@@ -824,8 +1042,8 @@ export class MongoStorageAdapter implements StorageAdapter {
updateSchemaWithIndexes(): Promise<any> {
return this.getAllClasses()
.then((classes) => {
const promises = classes.map((schema) => {
.then(classes => {
const promises = classes.map(schema => {
return this.setIndexesFromMongo(schema.className);
});
return Promise.all(promises);

File diff suppressed because it is too large Load Diff