-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Begin isolating object creation code into an externalizable API. #1569
Changes from 12 commits
4c8afc2
16a56bf
1641384
6870cb9
5681ab6
ec65663
7abfb5d
11fc616
4dadfa6
17cdbcd
ac3749b
3fceb22
740187a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,9 +21,13 @@ var Parse = require('parse/node').Parse; | |
// validate: true indicates that key names are to be validated. | ||
// | ||
// Returns an object with {key: key, value: value}. | ||
export function transformKeyValue(schema, className, restKey, restValue, options) { | ||
options = options || {}; | ||
|
||
export function transformKeyValue(schema, className, restKey, restValue, { | ||
inArray, | ||
inObject, | ||
query, | ||
update, | ||
validate, | ||
} = {}) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
// Check if the schema is known since it's a built-in field. | ||
var key = restKey; | ||
var timeField = false; | ||
|
@@ -62,7 +66,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options | |
return {key: key, value: restValue}; | ||
break; | ||
case '$or': | ||
if (!options.query) { | ||
if (!query) { | ||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, | ||
'you can only use $or in queries'); | ||
} | ||
|
@@ -75,7 +79,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options | |
}); | ||
return {key: '$or', value: mongoSubqueries}; | ||
case '$and': | ||
if (!options.query) { | ||
if (!query) { | ||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, | ||
'you can only use $and in queries'); | ||
} | ||
|
@@ -91,7 +95,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options | |
// Other auth data | ||
var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); | ||
if (authDataMatch) { | ||
if (options.query) { | ||
if (query) { | ||
var provider = authDataMatch[1]; | ||
// Special-case auth data. | ||
return {key: '_auth_data_'+provider+'.id', value: restValue}; | ||
|
@@ -100,7 +104,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options | |
'can only query on ' + key); | ||
break; | ||
}; | ||
if (options.validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { | ||
if (validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { | ||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, | ||
'invalid key name: ' + key); | ||
} | ||
|
@@ -117,24 +121,24 @@ export function transformKeyValue(schema, className, restKey, restValue, options | |
(!expected && restValue && restValue.__type == 'Pointer')) { | ||
key = '_p_' + key; | ||
} | ||
var inArray = (expected && expected.type === 'Array'); | ||
var expectedTypeIsArray = (expected && expected.type === 'Array'); | ||
|
||
// Handle query constraints | ||
if (options.query) { | ||
value = transformConstraint(restValue, inArray); | ||
if (query) { | ||
value = transformConstraint(restValue, expectedTypeIsArray); | ||
if (value !== CannotTransform) { | ||
return {key: key, value: value}; | ||
} | ||
} | ||
|
||
if (inArray && options.query && !(restValue instanceof Array)) { | ||
if (expectedTypeIsArray && query && !(restValue instanceof Array)) { | ||
return { | ||
key: key, value: { '$all' : [restValue] } | ||
}; | ||
} | ||
|
||
// Handle atomic values | ||
var value = transformAtom(restValue, false, options); | ||
var value = transformAtom(restValue, false, { inArray, inObject }); | ||
if (value !== CannotTransform) { | ||
if (timeField && (typeof value === 'string')) { | ||
value = new Date(value); | ||
|
@@ -150,7 +154,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options | |
|
||
// Handle arrays | ||
if (restValue instanceof Array) { | ||
if (options.query) { | ||
if (query) { | ||
throw new Parse.Error(Parse.Error.INVALID_JSON, | ||
'cannot use array as query param'); | ||
} | ||
|
@@ -162,7 +166,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options | |
} | ||
|
||
// Handle update operators | ||
value = transformUpdateOperator(restValue, !options.update); | ||
value = transformUpdateOperator(restValue, !update); | ||
if (value !== CannotTransform) { | ||
return {key: key, value: value}; | ||
} | ||
|
@@ -198,18 +202,114 @@ function transformWhere(schema, className, restWhere, options = {validate: true} | |
return mongoWhere; | ||
} | ||
|
||
const parseObjectKeyValueToMongoObjectKeyValue = ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not a function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. an arrow function, they permeate |
||
schema, | ||
className, | ||
restKey, | ||
restValue, | ||
parseFormatSchema | ||
) => { | ||
// Check if the schema is known since it's a built-in field. | ||
let transformedValue; | ||
let coercedToDate; | ||
switch(restKey) { | ||
case 'objectId': return {key: '_id', value: restValue}; | ||
case '_created_at'://TODO: for some reason, _PushStatus is already transformed when it gets here. For now, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should update the created at from _PushStatus as it's a bug that emerged when I moved it to use the DBController instead of the direct mongo access. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh? if _PushStatus is a Parse Object, it should have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's what I'm saying :) originally it was a pure mongo object, then I updated to use the DBController and it passed review without changing that. I'll do a PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah OK. Your original note was a little confusing. Can you wait for this to merge, then do a single PR that fixes the bug and also removed the special casing and TODOs from this function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No problem |
||
// just pass the _created_at through. Later, we should make sure the push status doesn't get transformed inside Parse Server. | ||
case 'createdAt': | ||
transformedValue = transformAtom(restValue, false); | ||
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue | ||
return {key: '_created_at', value: coercedToDate}; | ||
case 'updatedAt': | ||
transformedValue = transformAtom(restValue, false); | ||
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue | ||
return {key: '_updated_at', value: coercedToDate}; | ||
case 'expiresAt': | ||
transformedValue = transformAtom(restValue, false); | ||
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue | ||
return {key: 'expiresAt', value: coercedToDate}; | ||
case '_id': //TODO: for some reason, _PushStatus is already transformed when it gets here. For now, | ||
// just pass the ID through. Later, we should make sure the push status doesn't get transformed inside Parse Server. | ||
case '_rperm': | ||
case '_wperm': | ||
case '_email_verify_token': | ||
case '_hashed_password': | ||
case '_perishable_token': return {key: restKey, value: restValue}; | ||
case 'sessionToken': return {key: '_session_token', value: restValue}; | ||
default: | ||
// Auth data should have been transformed already | ||
if (restKey.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { | ||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'can only query on ' + restKey); | ||
} | ||
// Trust that the auth data has been transformed and save it directly | ||
if (restKey.match(/^_auth_data_[a-zA-Z0-9_]+$/)) { | ||
return {key: restKey, value: restValue}; | ||
} | ||
} | ||
//skip straight to transformAtom for Bytes, they don't show up in the schema for some reason | ||
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 | ||
//That never existed before, meaning we can't infer the type. | ||
if (parseFormatSchema.fields[restKey] && parseFormatSchema.fields[restKey].type == 'Pointer' || restValue.__type == 'Pointer') { | ||
restKey = '_p_' + restKey; | ||
} | ||
} | ||
|
||
// Handle atomic values | ||
var value = transformAtom(restValue, false, { inArray: false, inObject: false }); | ||
if (value !== CannotTransform) { | ||
return {key: restKey, value: value}; | ||
} | ||
|
||
// ACLs are handled before this method is called | ||
// If an ACL key still exists here, something is wrong. | ||
if (restKey === 'ACL') { | ||
throw 'There was a problem transforming an ACL.'; | ||
} | ||
|
||
// Handle arrays | ||
if (restValue instanceof Array) { | ||
value = restValue.map((restObj) => { | ||
var out = transformKeyValue(schema, className, restKey, restObj, { inArray: true }); | ||
return out.value; | ||
}); | ||
return {key: restKey, value: value}; | ||
} | ||
|
||
// Handle update operators | ||
value = transformUpdateOperator(restValue, true); | ||
if (value !== CannotTransform) { | ||
return {key: restKey, value: value}; | ||
} | ||
|
||
// Handle normal objects by recursing | ||
value = {}; | ||
for (var subRestKey in restValue) { | ||
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}; | ||
} | ||
|
||
// Main exposed method to create new objects. | ||
// restCreate is the "create" clause in REST API form. | ||
// Returns the mongo form of the object. | ||
function parseObjectToMongoObject(schema, className, restCreate) { | ||
function parseObjectToMongoObjectForCreate(schema, className, restCreate, parseFormatSchema) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why this renaming? Would the forUpdate be any different? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. forUpdate would handle the atomic update operators like increment, pull, pullAll, addUnique, etc. For new object creation, those don't make any sense. Currently they are handled here by translating them to just field creation (eg. increment by 1 just becomes 1), but they probably shouldn't be. Willing to reconsider though, if you have opinions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for the sake of maintenance, maybe having the 2 code paths can introduce problems. Not sure about that. I feel that having separate transforms depending on the state of the object (create, update) may introduce other issues. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd imagine the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we lose performance throwing atomic operators to mongo on not existing object? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think so. You would have to use |
||
if (className == '_User') { | ||
restCreate = transformAuthData(restCreate); | ||
} | ||
var mongoCreate = transformACL(restCreate); | ||
for (var restKey in restCreate) { | ||
var out = transformKeyValue(schema, className, restKey, restCreate[restKey]); | ||
if (out.value !== undefined) { | ||
mongoCreate[out.key] = out.value; | ||
for (let restKey in restCreate) { | ||
let { key, value } = parseObjectKeyValueToMongoObjectKeyValue( | ||
schema, | ||
className, | ||
restKey, | ||
restCreate[restKey], | ||
parseFormatSchema | ||
); | ||
if (value !== undefined) { | ||
mongoCreate[key] = value; | ||
} | ||
} | ||
return mongoCreate; | ||
|
@@ -933,7 +1033,7 @@ var FileCoder = { | |
|
||
module.exports = { | ||
transformKey, | ||
parseObjectToMongoObject, | ||
parseObjectToMongoObjectForCreate, | ||
transformUpdate, | ||
transformWhere, | ||
transformSelect, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from the adapter point of view, we should not need the schema, validation should be taken care of by the DBController.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is my thought as well. The legacy format needs to know if a field is a pointer though, because it saves them differently 😢 either way, later PR.