Skip to content

Commit 634d672

Browse files
committed
passing another test
1 parent 803b9be commit 634d672

File tree

6 files changed

+134
-34
lines changed

6 files changed

+134
-34
lines changed

spec/ParseAPI.spec.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ describe('miscellaneous', function() {
300300
});
301301
});
302302

303-
fit('succeed in logging in', function(done) {
303+
it('succeed in logging in', function(done) {
304304
createTestUser(function(u) {
305305
expect(typeof u.id).toEqual('string');
306306

@@ -318,7 +318,7 @@ describe('miscellaneous', function() {
318318
}, fail);
319319
});
320320

321-
it('increment with a user object', function(done) {
321+
fit('increment with a user object', function(done) {
322322
createTestUser().then((user) => {
323323
user.increment('foo');
324324
return user.save();
@@ -328,15 +328,14 @@ describe('miscellaneous', function() {
328328
expect(user.get('foo')).toEqual(1);
329329
user.increment('foo');
330330
return user.save();
331-
}).then(() => {
332-
Parse.User.logOut();
333-
return Parse.User.logIn('test', 'moon-y');
334-
}).then((user) => {
331+
}).then(() => Parse.User.logOut())
332+
.then(() => Parse.User.logIn('test', 'moon-y'))
333+
.then((user) => {
335334
expect(user.get('foo')).toEqual(2);
336335
Parse.User.logOut()
337336
.then(done);
338337
}, (error) => {
339-
fail(error);
338+
fail(JSON.stringify(error));
340339
done();
341340
});
342341
});

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export class MongoStorageAdapter {
232232
}
233233

234234
// Atomically finds and updates an object based on query.
235-
// Resolve with the updated object.
235+
// Return value not currently well specified.
236236
findOneAndUpdate(className, schema, query, update) {
237237
const mongoUpdate = transformUpdate(className, update, schema);
238238
const mongoWhere = transformWhere(className, query, schema);
@@ -255,7 +255,7 @@ export class MongoStorageAdapter {
255255
let mongoSort = _.mapKeys(sort, (value, fieldName) => transformKey(className, fieldName, schema));
256256
return this._adaptiveCollection(className)
257257
.then(collection => collection.find(mongoWhere, { skip, limit, sort: mongoSort }))
258-
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)));
258+
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)))
259259
}
260260

261261
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const parseTypeToPostgresType = type => {
1010
case 'Object': return 'jsonb';
1111
case 'Boolean': return 'boolean';
1212
case 'Pointer': return 'char(10)';
13+
case 'Number': return 'double precision';
1314
case 'Array':
1415
if (type.contents && type.contents.type === 'String') {
1516
return 'text[]';
@@ -69,8 +70,8 @@ export class PostgresStorageAdapter {
6970
}
7071

7172
addFieldIfNotExists(className, fieldName, type) {
72-
// TODO: Doing this in a transaction is probably a good idea.
73-
return this._client.query('ALTER TABLE $<className:name> ADD COLUMN $<fieldName:name> text', { className, fieldName })
73+
// TODO: Doing this in a transaction might be a good idea.
74+
return this._client.query('ALTER TABLE $<className:name> ADD COLUMN $<fieldName:name> $<postgresType:raw>', { className, fieldName, postgresType: parseTypeToPostgresType(type) })
7475
.catch(error => {
7576
if (error.code === PostgresRelationDoesNotExistError) {
7677
return this.createClass(className, { fields: { [fieldName]: type } })
@@ -161,7 +162,7 @@ export class PostgresStorageAdapter {
161162
});
162163
}
163164

164-
// TODO: remove the mongo format dependency
165+
// TODO: remove the mongo format dependency in the return value
165166
createObject(className, schema, object) {
166167
let columnsArray = [];
167168
let valuesArray = [];
@@ -181,7 +182,7 @@ export class PostgresStorageAdapter {
181182
});
182183
let columnsPattern = columnsArray.map((col, index) => `$${index + 2}:name`).join(',');
183184
let valuesPattern = valuesArray.map((val, index) => `$${index + 2 + columnsArray.length}`).join(',');
184-
return this._client.query(`INSERT INTO $1~ (${columnsPattern}) VALUES (${valuesPattern})`, [className, ...columnsArray, ...valuesArray])
185+
return this._client.query(`INSERT INTO $1:name (${columnsPattern}) VALUES (${valuesPattern})`, [className, ...columnsArray, ...valuesArray])
185186
.then(() => ({ ops: [object] }))
186187
}
187188

@@ -204,9 +205,53 @@ export class PostgresStorageAdapter {
204205
return Promise.reject('Not implented yet.')
205206
}
206207

207-
// Hopefully we can get rid of this in favor of updateObjectsByQuery.
208+
// Return value not currently well specified.
208209
findOneAndUpdate(className, schema, query, update) {
209-
return Promise.reject('Not implented yet.')
210+
let conditionPatterns = [];
211+
let updatePatterns = [];
212+
let values = []
213+
values.push(className);
214+
let index = 2;
215+
216+
for (let fieldName in update) {
217+
let fieldValue = update[fieldName];
218+
if (fieldValue.__op === 'Increment') {
219+
updatePatterns.push(`$${index}:name = COALESCE($${index}:name, 0) + $${index + 1}`);
220+
values.push(fieldName, fieldValue.amount);
221+
index += 2;
222+
} else if (fieldName === 'updatedAt') { //TODO: stop special casing this. It should check for __type === 'Date' and use .iso
223+
updatePatterns.push(`$${index}:name = $${index + 1}`)
224+
values.push(fieldName, new Date(fieldValue));
225+
index += 2;
226+
} else {
227+
return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, `Postgres doesn't support this type of update yet`));
228+
}
229+
}
230+
231+
for (let fieldName in query) {
232+
let fieldValue = query[fieldName];
233+
if (typeof fieldValue === 'string') {
234+
conditionPatterns.push(`$${index}:name = $${index + 1}`);
235+
values.push(fieldName, fieldValue);
236+
index += 2;
237+
} else if (Array.isArray(fieldValue.$in)) {
238+
let inPatterns = [];
239+
values.push(fieldName);
240+
fieldValue.$in.forEach((listElem, listIndex) => {
241+
values.push(listElem);
242+
inPatterns.push(`$${index + 1 + listIndex}`);
243+
});
244+
conditionPatterns.push(`$${index}:name && ARRAY[${inPatterns.join(',')}]`);
245+
index = index + 1 + inPatterns.length;
246+
} else {
247+
return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, `Postgres doesn't support this type of request yet`));
248+
}
249+
}
250+
let qs = `UPDATE $1:name SET ${updatePatterns.join(',')} WHERE ${conditionPatterns.join(' AND ')} RETURNING *`;
251+
return this._client.query(qs, values)
252+
.then(val => {
253+
return val[0];
254+
})
210255
}
211256

212257
// Hopefully we can get rid of this. It's only used for config and hooks.
@@ -216,11 +261,61 @@ export class PostgresStorageAdapter {
216261

217262
// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
218263
find(className, schema, query, { skip, limit, sort }) {
219-
return this._client.query("SELECT * FROM $<className:name>", { className })
264+
let conditionPatterns = [];
265+
let values = [];
266+
values.push(className);
267+
let index = 2;
268+
269+
for (let fieldName in query) {
270+
let fieldValue = query[fieldName];
271+
if (typeof fieldValue === 'string') {
272+
conditionPatterns.push(`$${index}:name = $${index + 1}`);
273+
values.push(fieldName, fieldValue);
274+
index += 2;
275+
} else if (fieldValue.$ne) {
276+
conditionPatterns.push(`$${index}:name <> $${index + 1}`);
277+
values.push(fieldName, fieldValue.$ne)
278+
index += 2;
279+
} else if (Array.isArray(fieldValue.$in)) {
280+
let inPatterns = [];
281+
values.push(fieldName);
282+
fieldValue.$in.forEach((listElem, listIndex) => {
283+
values.push(listElem);
284+
inPatterns.push(`$${index + 1 + listIndex}`);
285+
});
286+
conditionPatterns.push(`$${index}:name IN (${inPatterns.join(',')})`);
287+
index = index + 1 + inPatterns.length;
288+
} else if (fieldValue.__type === 'Pointer') {
289+
conditionPatterns.push(`$${index}:name = $${index + 1}`);
290+
values.push(fieldName, fieldValue.objectId);
291+
index += 2;
292+
} else {
293+
return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, "Postgres doesn't support this query type yet"));
294+
}
295+
}
296+
297+
return this._client.query(`SELECT * FROM $1:name WHERE ${conditionPatterns.join(' AND ')}`, values)
220298
.then(results => results.map(object => {
221299
Object.keys(schema.fields).filter(field => schema.fields[field].type === 'Pointer').forEach(fieldName => {
222300
object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass };
223301
});
302+
//TODO: remove this reliance on the mongo format. DB adapter shouldn't know there is a difference between created at and any other date field.
303+
if (object.createdAt) {
304+
object.createdAt = object.createdAt.toISOString();
305+
}
306+
if (object.updatedAt) {
307+
object.updatedAt = object.updatedAt.toISOString();
308+
}
309+
if (object.expiresAt) {
310+
object.expiresAt = { __type: 'Date', iso: object.expiresAt.toISOString() };
311+
}
312+
313+
for (let fieldName in object) {
314+
if (object[fieldName] === null) {
315+
delete object[fieldName];
316+
}
317+
}
318+
224319
return object;
225320
}))
226321
}

src/Controllers/DatabaseController.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ DatabaseController.prototype.update = function(className, query, update, {
240240
} else if (upsert) {
241241
return this.adapter.upsertOneObject(className, schema, query, update);
242242
} else {
243-
return this.adapter.findOneAndUpdate(className, schema, query, update);
243+
return this.adapter.findOneAndUpdate(className, schema, query, update)
244244
}
245245
});
246246
})
@@ -645,13 +645,15 @@ DatabaseController.prototype.find = function(className, query, {
645645
let isMaster = acl === undefined;
646646
let aclGroup = acl || [];
647647
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find';
648+
let classExists = true;
648649
return this.loadSchema()
649650
.then(schemaController => {
650651
return schemaController.getOneSchema(className)
651652
.catch(error => {
652-
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
653-
// will likely need revisiting.
653+
// Behaviour for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much.
654+
// For now, pretend the class exists but has no objects,
654655
if (error === undefined) {
656+
classExists = false;
655657
return { fields: {} };
656658
}
657659
throw error;
@@ -685,24 +687,31 @@ DatabaseController.prototype.find = function(className, query, {
685687
}
686688
if (!query) {
687689
if (op == 'get') {
688-
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
689-
'Object not found.'));
690+
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
690691
} else {
691-
return Promise.resolve([]);
692+
return [];
692693
}
693694
}
694695
if (!isMaster) {
695696
query = addReadACL(query, aclGroup);
696697
}
697698
validateQuery(query);
698699
if (count) {
699-
return this.adapter.count(className, schema, query);
700+
if (!classExists) {
701+
return 0;
702+
} else {
703+
return this.adapter.count(className, schema, query);
704+
}
700705
} else {
701-
return this.adapter.find(className, schema, query, { skip, limit, sort })
702-
.then(objects => objects.map(object => {
703-
object = untransformObjectACL(object);
704-
return filterSensitiveData(isMaster, aclGroup, className, object)
705-
}));
706+
if (!classExists) {
707+
return [];
708+
} else {
709+
return this.adapter.find(className, schema, query, { skip, limit, sort })
710+
.then(objects => objects.map(object => {
711+
object = untransformObjectACL(object);
712+
return filterSensitiveData(isMaster, aclGroup, className, object)
713+
}));
714+
}
706715
}
707716
});
708717
});

src/RestWrite.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ function RestWrite(config, auth, className, query, data, originalData) {
3131
this.runOptions = {};
3232

3333
if (!query && data.objectId) {
34-
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId ' +
35-
'is an invalid field name.');
34+
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.');
3635
}
3736

3837
// When the operation is complete, this.response may have several
@@ -712,8 +711,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
712711
if (this.className === '_User' &&
713712
this.query &&
714713
!this.auth.couldUpdateUserId(this.query.objectId)) {
715-
throw new Parse.Error(Parse.Error.SESSION_MISSING,
716-
'cannot modify user ' + this.query.objectId);
714+
throw new Parse.Error(Parse.Error.SESSION_MISSING, `Cannot modify user ${this.query.objectId}.`);
717715
}
718716

719717
if (this.className === '_Product' && this.data.download) {

src/rest.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,7 @@ function update(config, auth, className, objectId, restObject) {
117117
originalRestObject = response.results[0];
118118
}
119119

120-
var write = new RestWrite(config, auth, className,
121-
{objectId: objectId}, restObject, originalRestObject);
120+
var write = new RestWrite(config, auth, className, {objectId: objectId}, restObject, originalRestObject);
122121
return write.execute();
123122
});
124123
}

0 commit comments

Comments
 (0)