@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user