Refactor MongoTransform.js (#1823)
* Split transformAtom into transfromTopLevelAtom and transformInteriorAtom * Use single param for inArray and inObject * Tidyness in transformKeyValue * Add transformInteriorKeyValue * Remove update from tranformInteriorKeyValue * Split out transform update * Move validation out of transfromUpdate * Remove force paramater from transformTopLevelAtom throw error after if necessary * Turn transformKeyValue into transfromKey since it is only used for that purpose * Remove unnecessary stuff from transformKey * convert transformKey to use parse format schema * interior keys fixes * Add test for interior keys with special names * Correct validation of inner keys
This commit is contained in:
@@ -1437,4 +1437,36 @@ describe('miscellaneous', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('doesnt convert interior keys of objects that use special names', done => {
|
||||||
|
let obj = new Parse.Object('Obj');
|
||||||
|
obj.set('val', { createdAt: 'a', updatedAt: 1 });
|
||||||
|
obj.save()
|
||||||
|
.then(obj => new Parse.Query('Obj').get(obj.id))
|
||||||
|
.then(obj => {
|
||||||
|
expect(obj.get('val').createdAt).toEqual('a');
|
||||||
|
expect(obj.get('val').updatedAt).toEqual(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bans interior keys containing . or $', done => {
|
||||||
|
new Parse.Object('Obj').save({innerObj: {'key with a $': 'fails'}})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
|
||||||
|
return new Parse.Object('Obj').save({innerObj: {'key with a .': 'fails'}});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
|
||||||
|
return new Parse.Object('Obj').save({innerObj: {innerInnerObj: {'key with $': 'fails'}}});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
|
||||||
|
return new Parse.Object('Obj').save({innerObj: {innerInnerObj: {'key with .': 'fails'}}});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,24 +3,23 @@ import _ from 'lodash';
|
|||||||
var mongodb = require('mongodb');
|
var mongodb = require('mongodb');
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
|
|
||||||
// Transforms a key-value pair from REST API form to Mongo form.
|
const transformKey = (className, fieldName, schema) => {
|
||||||
// This is the main entry point for converting anything from REST form
|
// Check if the schema is known since it's a built-in field.
|
||||||
// to Mongo form; no conversion should happen that doesn't pass
|
switch(fieldName) {
|
||||||
// through this function.
|
case 'objectId': return '_id';
|
||||||
// Schema should already be loaded.
|
case 'createdAt': return '_created_at';
|
||||||
//
|
case 'updatedAt': return '_updated_at';
|
||||||
// There are several options that can help transform:
|
case 'sessionToken': return '_session_token';
|
||||||
//
|
}
|
||||||
// update: true indicates that __op operators like Add and Delete
|
|
||||||
// in the value are converted to a mongo update form. Otherwise they are
|
if (schema.fields[fieldName] && schema.fields[fieldName].__type == 'Pointer') {
|
||||||
// converted to static data.
|
fieldName = '_p_' + fieldName;
|
||||||
//
|
}
|
||||||
// Returns an object with {key: key, value: value}.
|
|
||||||
function transformKeyValue(schema, className, restKey, restValue, {
|
return fieldName;
|
||||||
inArray,
|
}
|
||||||
inObject,
|
|
||||||
update,
|
const transformKeyValueForUpdate = (schema, className, restKey, restValue) => {
|
||||||
} = {}) {
|
|
||||||
// Check if the schema is known since it's a built-in field.
|
// Check if the schema is known since it's a built-in field.
|
||||||
var key = restKey;
|
var key = restKey;
|
||||||
var timeField = false;
|
var timeField = false;
|
||||||
@@ -77,51 +76,60 @@ function transformKeyValue(schema, className, restKey, restValue, {
|
|||||||
if (schema && schema.getExpectedType) {
|
if (schema && schema.getExpectedType) {
|
||||||
expected = schema.getExpectedType(className, key);
|
expected = schema.getExpectedType(className, key);
|
||||||
}
|
}
|
||||||
if ((expected && expected.type == 'Pointer') ||
|
if ((expected && expected.type == 'Pointer') || (!expected && restValue && restValue.__type == 'Pointer')) {
|
||||||
(!expected && restValue && restValue.__type == 'Pointer')) {
|
|
||||||
key = '_p_' + key;
|
key = '_p_' + key;
|
||||||
}
|
}
|
||||||
var expectedTypeIsArray = (expected && expected.type === 'Array');
|
|
||||||
|
|
||||||
// Handle atomic values
|
// Handle atomic values
|
||||||
var value = transformAtom(restValue, false, { inArray, inObject });
|
var value = transformTopLevelAtom(restValue);
|
||||||
if (value !== CannotTransform) {
|
if (value !== CannotTransform) {
|
||||||
if (timeField && (typeof value === 'string')) {
|
if (timeField && (typeof value === 'string')) {
|
||||||
value = new Date(value);
|
value = new Date(value);
|
||||||
}
|
}
|
||||||
return {key: key, value: value};
|
return {key, value};
|
||||||
}
|
|
||||||
|
|
||||||
// ACLs are handled before this method is called
|
|
||||||
// If an ACL key still exists here, something is wrong.
|
|
||||||
if (key === 'ACL') {
|
|
||||||
throw 'There was a problem transforming an ACL.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle arrays
|
// Handle arrays
|
||||||
if (restValue instanceof Array) {
|
if (restValue instanceof Array) {
|
||||||
value = restValue.map((restObj) => {
|
value = restValue.map(transformInteriorValue);
|
||||||
var out = transformKeyValue(schema, className, restKey, restObj, { inArray: true });
|
return {key, value};
|
||||||
return out.value;
|
|
||||||
});
|
|
||||||
return {key: key, value: value};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle update operators
|
// Handle update operators
|
||||||
value = transformUpdateOperator(restValue, !update);
|
if (typeof restValue === 'object' && '__op' in restValue) {
|
||||||
if (value !== CannotTransform) {
|
return {key, value: transformUpdateOperator(restValue, false)};
|
||||||
return {key: key, value: value};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle normal objects by recursing
|
// Handle normal objects by recursing
|
||||||
value = {};
|
if (Object.keys(restValue).some(key => key.includes('$') || key.includes('.'))) {
|
||||||
for (var subRestKey in restValue) {
|
throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters");
|
||||||
var subRestValue = restValue[subRestKey];
|
|
||||||
var out = transformKeyValue(schema, className, subRestKey, subRestValue, { inObject: true });
|
|
||||||
// For recursed objects, keep the keys in rest format
|
|
||||||
value[subRestKey] = out.value;
|
|
||||||
}
|
}
|
||||||
return {key: key, value: value};
|
value = _.mapValues(restValue, transformInteriorValue);
|
||||||
|
return {key, value};
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformInteriorValue = restValue => {
|
||||||
|
if (typeof restValue === 'object' && Object.keys(restValue).some(key => key.includes('$') || key.includes('.'))) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters");
|
||||||
|
}
|
||||||
|
// Handle atomic values
|
||||||
|
var value = transformInteriorAtom(restValue);
|
||||||
|
if (value !== CannotTransform) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle arrays
|
||||||
|
if (restValue instanceof Array) {
|
||||||
|
return restValue.map(transformInteriorValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle update operators
|
||||||
|
if (typeof restValue === 'object' && '__op' in restValue) {
|
||||||
|
return transformUpdateOperator(restValue, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle normal objects by recursing
|
||||||
|
return _.mapValues(restValue, transformInteriorValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueAsDate = value => {
|
const valueAsDate = value => {
|
||||||
@@ -205,8 +213,8 @@ function transformQueryKeyValue(className, key, value, { validate } = {}, schema
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle atomic values
|
// Handle atomic values
|
||||||
if (transformAtom(value, false) !== CannotTransform) {
|
if (transformTopLevelAtom(value) !== CannotTransform) {
|
||||||
return {key, value: transformAtom(value, false)};
|
return {key, value: transformTopLevelAtom(value)};
|
||||||
} else {
|
} else {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON, `You cannot use ${value} as a query parameter.`);
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `You cannot use ${value} as a query parameter.`);
|
||||||
}
|
}
|
||||||
@@ -241,15 +249,15 @@ const parseObjectKeyValueToMongoObjectKeyValue = (
|
|||||||
switch(restKey) {
|
switch(restKey) {
|
||||||
case 'objectId': return {key: '_id', value: restValue};
|
case 'objectId': return {key: '_id', value: restValue};
|
||||||
case 'createdAt':
|
case 'createdAt':
|
||||||
transformedValue = transformAtom(restValue, false);
|
transformedValue = transformTopLevelAtom(restValue);
|
||||||
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
||||||
return {key: '_created_at', value: coercedToDate};
|
return {key: '_created_at', value: coercedToDate};
|
||||||
case 'updatedAt':
|
case 'updatedAt':
|
||||||
transformedValue = transformAtom(restValue, false);
|
transformedValue = transformTopLevelAtom(restValue);
|
||||||
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
||||||
return {key: '_updated_at', value: coercedToDate};
|
return {key: '_updated_at', value: coercedToDate};
|
||||||
case 'expiresAt':
|
case 'expiresAt':
|
||||||
transformedValue = transformAtom(restValue, false);
|
transformedValue = transformTopLevelAtom(restValue);
|
||||||
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
||||||
return {key: 'expiresAt', value: coercedToDate};
|
return {key: 'expiresAt', value: coercedToDate};
|
||||||
case '_rperm':
|
case '_rperm':
|
||||||
@@ -268,7 +276,7 @@ const parseObjectKeyValueToMongoObjectKeyValue = (
|
|||||||
return {key: restKey, value: restValue};
|
return {key: restKey, value: restValue};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//skip straight to transformAtom for Bytes, they don't show up in the schema for some reason
|
//skip straight to transformTopLevelAtom for Bytes, they don't show up in the schema for some reason
|
||||||
if (restValue && restValue.__type !== 'Bytes') {
|
if (restValue && restValue.__type !== 'Bytes') {
|
||||||
//Note: We may not know the type of a field here, as the user could be saving (null) to a field
|
//Note: We may not know the type of a field here, as the user could be saving (null) to a field
|
||||||
//That never existed before, meaning we can't infer the type.
|
//That never existed before, meaning we can't infer the type.
|
||||||
@@ -278,7 +286,7 @@ const parseObjectKeyValueToMongoObjectKeyValue = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle atomic values
|
// Handle atomic values
|
||||||
var value = transformAtom(restValue, false, { inArray: false, inObject: false });
|
var value = transformTopLevelAtom(restValue);
|
||||||
if (value !== CannotTransform) {
|
if (value !== CannotTransform) {
|
||||||
return {key: restKey, value: value};
|
return {key: restKey, value: value};
|
||||||
}
|
}
|
||||||
@@ -291,28 +299,21 @@ const parseObjectKeyValueToMongoObjectKeyValue = (
|
|||||||
|
|
||||||
// Handle arrays
|
// Handle arrays
|
||||||
if (restValue instanceof Array) {
|
if (restValue instanceof Array) {
|
||||||
value = restValue.map((restObj) => {
|
value = restValue.map(transformInteriorValue);
|
||||||
var out = transformKeyValue(schema, className, restKey, restObj, { inArray: true });
|
|
||||||
return out.value;
|
|
||||||
});
|
|
||||||
return {key: restKey, value: value};
|
return {key: restKey, value: value};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle update operators. TODO: handle within Parse Server. DB adapter shouldn't see update operators in creates.
|
// Handle update operators. TODO: handle within Parse Server. DB adapter shouldn't see update operators in creates.
|
||||||
value = transformUpdateOperator(restValue, true);
|
if (typeof restValue === 'object' && '__op' in restValue) {
|
||||||
if (value !== CannotTransform) {
|
return {key: restKey, value: transformUpdateOperator(restValue, true)};
|
||||||
return {key: restKey, value: value};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle normal objects by recursing
|
// Handle normal objects by recursing
|
||||||
value = {};
|
if (Object.keys(restValue).some(key => key.includes('$') || key.includes('.'))) {
|
||||||
for (var subRestKey in restValue) {
|
throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters");
|
||||||
var subRestValue = restValue[subRestKey];
|
|
||||||
var out = transformKeyValue(schema, className, subRestKey, subRestValue, { inObject: true });
|
|
||||||
// For recursed objects, keep the keys in rest format
|
|
||||||
value[subRestKey] = out.value;
|
|
||||||
}
|
}
|
||||||
return {key: restKey, value: value};
|
value = _.mapValues(restValue, transformInteriorValue);
|
||||||
|
return {key: restKey, value};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main exposed method to create new objects.
|
// Main exposed method to create new objects.
|
||||||
@@ -362,13 +363,12 @@ function transformUpdate(schema, className, restUpdate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (var restKey in restUpdate) {
|
for (var restKey in restUpdate) {
|
||||||
var out = transformKeyValue(schema, className, restKey, restUpdate[restKey], {update: true});
|
var out = transformKeyValueForUpdate(schema, className, restKey, restUpdate[restKey]);
|
||||||
|
|
||||||
// If the output value is an object with any $ keys, it's an
|
// If the output value is an object with any $ keys, it's an
|
||||||
// operator that needs to be lifted onto the top level update
|
// operator that needs to be lifted onto the top level update
|
||||||
// object.
|
// object.
|
||||||
if (typeof out.value === 'object' && out.value !== null &&
|
if (typeof out.value === 'object' && out.value !== null && out.value.__op) {
|
||||||
out.value.__op) {
|
|
||||||
mongoUpdate[out.value.__op] = mongoUpdate[out.value.__op] || {};
|
mongoUpdate[out.value.__op] = mongoUpdate[out.value.__op] || {};
|
||||||
mongoUpdate[out.value.__op][out.key] = out.value.arg;
|
mongoUpdate[out.value.__op][out.key] = out.value.arg;
|
||||||
} else {
|
} else {
|
||||||
@@ -462,20 +462,33 @@ function untransformACL(mongoObject) {
|
|||||||
// cannot perform a transformation
|
// cannot perform a transformation
|
||||||
function CannotTransform() {}
|
function CannotTransform() {}
|
||||||
|
|
||||||
|
const transformInteriorAtom = atom => {
|
||||||
|
// TODO: check validity harder for the __type-defined types
|
||||||
|
if (typeof atom === 'object' && atom && !(atom instanceof Date) && atom.__type === 'Pointer') {
|
||||||
|
return {
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: atom.className,
|
||||||
|
objectId: atom.objectId
|
||||||
|
};
|
||||||
|
} else if (typeof atom === 'function' || typeof atom === 'symbol') {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `cannot transform value: ${atom}`);
|
||||||
|
} else if (DateCoder.isValidJSON(atom)) {
|
||||||
|
return DateCoder.JSONToDatabase(atom);
|
||||||
|
} else if (BytesCoder.isValidJSON(atom)) {
|
||||||
|
return BytesCoder.JSONToDatabase(atom);
|
||||||
|
} else {
|
||||||
|
return atom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to transform an atom from REST format to Mongo format.
|
// Helper function to transform an atom from REST format to Mongo format.
|
||||||
// An atom is anything that can't contain other expressions. So it
|
// An atom is anything that can't contain other expressions. So it
|
||||||
// includes things where objects are used to represent other
|
// includes things where objects are used to represent other
|
||||||
// datatypes, like pointers and dates, but it does not include objects
|
// datatypes, like pointers and dates, but it does not include objects
|
||||||
// or arrays with generic stuff inside.
|
// or arrays with generic stuff inside.
|
||||||
// If options.inArray is true, we'll leave it in REST format.
|
|
||||||
// If options.inObject is true, we'll leave files in REST format.
|
|
||||||
// Raises an error if this cannot possibly be valid REST format.
|
// Raises an error if this cannot possibly be valid REST format.
|
||||||
// Returns CannotTransform if it's just not an atom, or if force is
|
// Returns CannotTransform if it's just not an atom
|
||||||
// true, throws an error.
|
function transformTopLevelAtom(atom) {
|
||||||
function transformAtom(atom, force, {
|
|
||||||
inArray,
|
|
||||||
inObject,
|
|
||||||
} = {}) {
|
|
||||||
switch(typeof atom) {
|
switch(typeof atom) {
|
||||||
case 'string':
|
case 'string':
|
||||||
case 'number':
|
case 'number':
|
||||||
@@ -499,14 +512,7 @@ function transformAtom(atom, force, {
|
|||||||
|
|
||||||
// TODO: check validity harder for the __type-defined types
|
// TODO: check validity harder for the __type-defined types
|
||||||
if (atom.__type == 'Pointer') {
|
if (atom.__type == 'Pointer') {
|
||||||
if (!inArray && !inObject) {
|
return `${atom.className}$${atom.objectId}`;
|
||||||
return `${atom.className}$${atom.objectId}`;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
__type: 'Pointer',
|
|
||||||
className: atom.className,
|
|
||||||
objectId: atom.objectId
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if (DateCoder.isValidJSON(atom)) {
|
if (DateCoder.isValidJSON(atom)) {
|
||||||
return DateCoder.JSONToDatabase(atom);
|
return DateCoder.JSONToDatabase(atom);
|
||||||
@@ -515,17 +521,10 @@ function transformAtom(atom, force, {
|
|||||||
return BytesCoder.JSONToDatabase(atom);
|
return BytesCoder.JSONToDatabase(atom);
|
||||||
}
|
}
|
||||||
if (GeoPointCoder.isValidJSON(atom)) {
|
if (GeoPointCoder.isValidJSON(atom)) {
|
||||||
return (inArray || inObject ? atom : GeoPointCoder.JSONToDatabase(atom));
|
return GeoPointCoder.JSONToDatabase(atom);
|
||||||
}
|
}
|
||||||
if (FileCoder.isValidJSON(atom)) {
|
if (FileCoder.isValidJSON(atom)) {
|
||||||
return (inArray || inObject ? atom : FileCoder.JSONToDatabase(atom));
|
return FileCoder.JSONToDatabase(atom);
|
||||||
}
|
|
||||||
if (inArray || inObject) {
|
|
||||||
return atom;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (force) {
|
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad atom: ${atom}`);
|
|
||||||
}
|
}
|
||||||
return CannotTransform;
|
return CannotTransform;
|
||||||
|
|
||||||
@@ -560,19 +559,24 @@ function transformConstraint(constraint, inArray) {
|
|||||||
case '$exists':
|
case '$exists':
|
||||||
case '$ne':
|
case '$ne':
|
||||||
case '$eq':
|
case '$eq':
|
||||||
answer[key] = transformAtom(constraint[key], true,
|
answer[key] = inArray ? transformInteriorAtom(constraint[key]) : transformTopLevelAtom(constraint[key]);
|
||||||
{inArray: inArray});
|
if (answer[key] === CannotTransform) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad atom: ${atom}`);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '$in':
|
case '$in':
|
||||||
case '$nin':
|
case '$nin':
|
||||||
var arr = constraint[key];
|
var arr = constraint[key];
|
||||||
if (!(arr instanceof Array)) {
|
if (!(arr instanceof Array)) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value');
|
||||||
'bad ' + key + ' value');
|
|
||||||
}
|
}
|
||||||
answer[key] = arr.map((v) => {
|
answer[key] = arr.map(value => {
|
||||||
return transformAtom(v, true, { inArray: inArray });
|
let result = inArray ? transformInteriorAtom(value) : transformTopLevelAtom(value);
|
||||||
|
if (result === CannotTransform) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad atom: ${atom}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -582,9 +586,7 @@ function transformConstraint(constraint, inArray) {
|
|||||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||||
'bad ' + key + ' value');
|
'bad ' + key + ' value');
|
||||||
}
|
}
|
||||||
answer[key] = arr.map((v) => {
|
answer[key] = arr.map(transformInteriorAtom);
|
||||||
return transformAtom(v, true, { inArray: true });
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '$regex':
|
case '$regex':
|
||||||
@@ -667,14 +669,14 @@ function transformConstraint(constraint, inArray) {
|
|||||||
// The output for a non-flattened operator is a hash with __op being
|
// The output for a non-flattened operator is a hash with __op being
|
||||||
// the mongo op, and arg being the argument.
|
// the mongo op, and arg being the argument.
|
||||||
// The output for a flattened operator is just a value.
|
// The output for a flattened operator is just a value.
|
||||||
// Returns CannotTransform if this cannot transform it.
|
|
||||||
// Returns undefined if this should be a no-op.
|
// Returns undefined if this should be a no-op.
|
||||||
function transformUpdateOperator(operator, flatten) {
|
|
||||||
if (typeof operator !== 'object' || !operator.__op) {
|
|
||||||
return CannotTransform;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(operator.__op) {
|
function transformUpdateOperator({
|
||||||
|
__op,
|
||||||
|
amount,
|
||||||
|
objects,
|
||||||
|
}, flatten) {
|
||||||
|
switch(__op) {
|
||||||
case 'Delete':
|
case 'Delete':
|
||||||
if (flatten) {
|
if (flatten) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -683,43 +685,36 @@ function transformUpdateOperator(operator, flatten) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'Increment':
|
case 'Increment':
|
||||||
if (typeof operator.amount !== 'number') {
|
if (typeof amount !== 'number') {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
throw new Parse.Error(Parse.Error.INVALID_JSON, 'incrementing must provide a number');
|
||||||
'incrementing must provide a number');
|
|
||||||
}
|
}
|
||||||
if (flatten) {
|
if (flatten) {
|
||||||
return operator.amount;
|
return amount;
|
||||||
} else {
|
} else {
|
||||||
return {__op: '$inc', arg: operator.amount};
|
return {__op: '$inc', arg: amount};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Add':
|
case 'Add':
|
||||||
case 'AddUnique':
|
case 'AddUnique':
|
||||||
if (!(operator.objects instanceof Array)) {
|
if (!(objects instanceof Array)) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array');
|
||||||
'objects to add must be an array');
|
|
||||||
}
|
}
|
||||||
var toAdd = operator.objects.map((obj) => {
|
var toAdd = objects.map(transformInteriorAtom);
|
||||||
return transformAtom(obj, true, { inArray: true });
|
|
||||||
});
|
|
||||||
if (flatten) {
|
if (flatten) {
|
||||||
return toAdd;
|
return toAdd;
|
||||||
} else {
|
} else {
|
||||||
var mongoOp = {
|
var mongoOp = {
|
||||||
Add: '$push',
|
Add: '$push',
|
||||||
AddUnique: '$addToSet'
|
AddUnique: '$addToSet'
|
||||||
}[operator.__op];
|
}[__op];
|
||||||
return {__op: mongoOp, arg: {'$each': toAdd}};
|
return {__op: mongoOp, arg: {'$each': toAdd}};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Remove':
|
case 'Remove':
|
||||||
if (!(operator.objects instanceof Array)) {
|
if (!(objects instanceof Array)) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to remove must be an array');
|
||||||
'objects to remove must be an array');
|
|
||||||
}
|
}
|
||||||
var toRemove = operator.objects.map((obj) => {
|
var toRemove = objects.map(transformInteriorAtom);
|
||||||
return transformAtom(obj, true, { inArray: true });
|
|
||||||
});
|
|
||||||
if (flatten) {
|
if (flatten) {
|
||||||
return [];
|
return [];
|
||||||
} else {
|
} else {
|
||||||
@@ -727,9 +722,7 @@ function transformUpdateOperator(operator, flatten) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, `The ${__op} operator is not supported yet.`);
|
||||||
Parse.Error.COMMAND_UNAVAILABLE,
|
|
||||||
'the ' + operator.__op + ' op is not supported yet');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1037,7 +1030,7 @@ var FileCoder = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
transformKeyValue,
|
transformKey,
|
||||||
parseObjectToMongoObjectForCreate,
|
parseObjectToMongoObjectForCreate,
|
||||||
transformUpdate,
|
transformUpdate,
|
||||||
transformWhere,
|
transformWhere,
|
||||||
|
|||||||
@@ -616,64 +616,67 @@ DatabaseController.prototype.find = function(className, query, {
|
|||||||
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find';
|
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find';
|
||||||
return this.loadSchema()
|
return this.loadSchema()
|
||||||
.then(schemaController => {
|
.then(schemaController => {
|
||||||
if (sort) {
|
return schemaController.getOneSchema(className)
|
||||||
mongoOptions.sort = {};
|
.catch(error => {
|
||||||
for (let fieldName in sort) {
|
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
||||||
// Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt,
|
// will likely need revisiting.
|
||||||
// so duplicate that behaviour here.
|
if (error === undefined) {
|
||||||
if (fieldName === '_created_at') {
|
return { fields: {} };
|
||||||
fieldName = 'createdAt';
|
}
|
||||||
sort['createdAt'] = sort['_created_at'];
|
throw error;
|
||||||
} else if (fieldName === '_updated_at') {
|
})
|
||||||
fieldName = 'updatedAt';
|
.then(schema => {
|
||||||
sort['updatedAt'] = sort['_updated_at'];
|
if (sort) {
|
||||||
}
|
mongoOptions.sort = {};
|
||||||
|
for (let fieldName in sort) {
|
||||||
|
// Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt,
|
||||||
|
// so duplicate that behaviour here.
|
||||||
|
if (fieldName === '_created_at') {
|
||||||
|
fieldName = 'createdAt';
|
||||||
|
sort['createdAt'] = sort['_created_at'];
|
||||||
|
} else if (fieldName === '_updated_at') {
|
||||||
|
fieldName = 'updatedAt';
|
||||||
|
sort['updatedAt'] = sort['_updated_at'];
|
||||||
|
}
|
||||||
|
|
||||||
if (!SchemaController.fieldNameIsValid(fieldName)) {
|
if (!SchemaController.fieldNameIsValid(fieldName)) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
|
||||||
}
|
}
|
||||||
const mongoKey = this.transform.transformKeyValue(schemaController, className, fieldName, null).key;
|
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
||||||
mongoOptions.sort[mongoKey] = sort[fieldName];
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`);
|
||||||
}
|
}
|
||||||
}
|
const mongoKey = this.transform.transformKey(className, fieldName, schema);
|
||||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, op))
|
mongoOptions.sort[mongoKey] = sort[fieldName];
|
||||||
.then(() => this.reduceRelationKeys(className, query))
|
|
||||||
.then(() => this.reduceInRelation(className, query, schemaController))
|
|
||||||
.then(() => this.adapter.adaptiveCollection(className))
|
|
||||||
.then(collection => {
|
|
||||||
if (!isMaster) {
|
|
||||||
query = this.addPointerPermissions(schemaController, className, op, query, aclGroup);
|
|
||||||
}
|
|
||||||
if (!query) {
|
|
||||||
if (op == 'get') {
|
|
||||||
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
|
||||||
'Object not found.'));
|
|
||||||
} else {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isMaster) {
|
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, op))
|
||||||
query = addReadACL(query, aclGroup);
|
.then(() => this.reduceRelationKeys(className, query))
|
||||||
}
|
.then(() => this.reduceInRelation(className, query, schemaController))
|
||||||
return schemaController.getOneSchema(className)
|
.then(() => this.adapter.adaptiveCollection(className))
|
||||||
.catch(error => {
|
.then(collection => {
|
||||||
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
if (!isMaster) {
|
||||||
// will likely need revisiting.
|
query = this.addPointerPermissions(schemaController, className, op, query, aclGroup);
|
||||||
if (error === undefined) {
|
|
||||||
return { fields: {} };
|
|
||||||
}
|
}
|
||||||
throw error;
|
if (!query) {
|
||||||
})
|
if (op == 'get') {
|
||||||
.then(parseFormatSchema => {
|
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||||
let mongoWhere = this.transform.transformWhere(className, query, {}, parseFormatSchema);
|
'Object not found.'));
|
||||||
|
} else {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isMaster) {
|
||||||
|
query = addReadACL(query, aclGroup);
|
||||||
|
}
|
||||||
|
let mongoWhere = this.transform.transformWhere(className, query, {}, schema);
|
||||||
if (count) {
|
if (count) {
|
||||||
delete mongoOptions.limit;
|
delete mongoOptions.limit;
|
||||||
return collection.count(mongoWhere, mongoOptions);
|
return collection.count(mongoWhere, mongoOptions);
|
||||||
} else {
|
} else {
|
||||||
return collection.find(mongoWhere, mongoOptions)
|
return collection.find(mongoWhere, mongoOptions)
|
||||||
.then((mongoResults) => {
|
.then(mongoResults => {
|
||||||
return mongoResults.map((r) => {
|
return mongoResults.map(result => {
|
||||||
return this.untransformObject(schemaController, isMaster, aclGroup, className, r);
|
return this.untransformObject(schemaController, isMaster, aclGroup, className, result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user