Skip to content

Commit 508fce7

Browse files
authored
Merge branch 'master' into flow-type-storage-adapter
2 parents a777243 + f0f1870 commit 508fce7

File tree

6 files changed

+103
-44
lines changed

6 files changed

+103
-44
lines changed

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
"intersect": "1.0.1",
3131
"lodash": "4.17.4",
3232
"lru-cache": "4.1.1",
33-
"mime": "2.0.5",
34-
"mongodb": "2.2.33",
33+
"mime": "2.1.0",
34+
"mongodb": "3.0.1",
3535
"multer": "1.3.0",
3636
"parse": "1.11.0",
3737
"pg-promise": "7.3.2",
@@ -61,7 +61,7 @@
6161
"jasmine": "2.8.0",
6262
"jasmine-spec-reporter": "^4.1.0",
6363
"mongodb-runner": "3.6.1",
64-
"nodemon": "1.12.1",
64+
"nodemon": "1.14.6",
6565
"nyc": "^11.0.2",
6666
"request-promise": "4.2.2"
6767
},

spec/ParseQuery.Aggregate.spec.js

+17
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,23 @@ describe('Parse.Query Aggregate testing', () => {
392392
}).catch(done.fail);
393393
});
394394

395+
it('distinct pointer', (done) => {
396+
const pointer1 = new TestObject();
397+
const pointer2 = new TestObject();
398+
const obj1 = new TestObject({ pointer: pointer1 });
399+
const obj2 = new TestObject({ pointer: pointer2 });
400+
const obj3 = new TestObject({ pointer: pointer1 });
401+
Parse.Object.saveAll([pointer1, pointer2, obj1, obj2, obj3]).then(() => {
402+
const query = new Parse.Query(TestObject);
403+
return query.distinct('pointer');
404+
}).then((results) => {
405+
expect(results.length).toEqual(2);
406+
expect(results.some(result => result.objectId === pointer1.id)).toEqual(true);
407+
expect(results.some(result => result.objectId === pointer2.id)).toEqual(true);
408+
done();
409+
});
410+
});
411+
395412
it('distinct class does not exist return empty', (done) => {
396413
const options = Object.assign({}, masterKeyOptions, {
397414
body: { distinct: 'unknown' }

src/Adapters/Files/GridStoreAdapter.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@ export class GridStoreAdapter extends FilesAdapter {
2222

2323
_connect() {
2424
if (!this._connectionPromise) {
25-
this._connectionPromise = MongoClient.connect(this._databaseURI);
25+
this._connectionPromise = MongoClient.connect(this._databaseURI)
26+
.then((client) => client.db(client.s.options.dbName));
2627
}
2728
return this._connectionPromise;
2829
}
2930

3031
// For a given config object, filename, and data, store a file
3132
// Returns a promise
3233
createFile(filename: string, data) {
33-
return this._connect().then(database => {
34+
return this._connect().then((database) => {
3435
const gridStore = new GridStore(database, filename, 'w');
3536
return gridStore.open();
3637
}).then(gridStore => {

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

+43-24
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
transformKey,
1717
transformWhere,
1818
transformUpdate,
19+
transformPointerString,
1920
} from './MongoTransform';
2021
// @flow-disable-next
2122
import Parse from 'parse/node';
@@ -132,7 +133,12 @@ export class MongoStorageAdapter implements StorageAdapter {
132133
// encoded
133134
const encodedUri = formatUrl(parseUrl(this._uri));
134135

135-
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions).then(database => {
136+
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions).then(client => {
137+
// Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client
138+
// Fortunately, we can get back the options and use them to select the proper DB.
139+
// https://github.com/mongodb/node-mongodb-native/blob/2c35d76f08574225b8db02d7bef687123e6bb018/lib/mongo_client.js#L885
140+
const options = client.s.options;
141+
const database = client.db(options.dbName);
136142
if (!database) {
137143
delete this.connectionPromise;
138144
return;
@@ -143,6 +149,7 @@ export class MongoStorageAdapter implements StorageAdapter {
143149
database.on('close', () => {
144150
delete this.connectionPromise;
145151
});
152+
this.client = client;
146153
this.database = database;
147154
}).catch((err) => {
148155
delete this.connectionPromise;
@@ -153,10 +160,10 @@ export class MongoStorageAdapter implements StorageAdapter {
153160
}
154161

155162
handleShutdown() {
156-
if (!this.database) {
163+
if (!this.client) {
157164
return;
158165
}
159-
this.database.close(false);
166+
this.client.close(false);
160167
}
161168

162169
_adaptiveCollection(name: string) {
@@ -491,9 +498,19 @@ export class MongoStorageAdapter implements StorageAdapter {
491498

492499
distinct(className: string, schema: SchemaType, query: QueryType, fieldName: string) {
493500
schema = convertParseSchemaToMongoSchema(schema);
501+
const isPointerField = schema.fields[fieldName] && schema.fields[fieldName].type === 'Pointer';
502+
if (isPointerField) {
503+
fieldName = `_p_${fieldName}`
504+
}
494505
return this._adaptiveCollection(className)
495506
.then(collection => collection.distinct(fieldName, transformWhere(className, query, schema)))
496-
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)));
507+
.then(objects => objects.map(object => {
508+
if (isPointerField) {
509+
const field = fieldName.substring(3);
510+
return transformPointerString(schema, field, object);
511+
}
512+
return mongoObjectToParseObject(className, object, schema);
513+
}));
497514
}
498515

499516
aggregate(className: string, schema: any, pipeline: any, readPreference: ?string) {
@@ -513,26 +530,28 @@ export class MongoStorageAdapter implements StorageAdapter {
513530
}
514531

515532
_parseReadPreference(readPreference: ?string): ?string {
516-
if (readPreference != null) {
517-
switch (readPreference) {
518-
case 'PRIMARY':
519-
readPreference = ReadPreference.PRIMARY;
520-
break;
521-
case 'PRIMARY_PREFERRED':
522-
readPreference = ReadPreference.PRIMARY_PREFERRED;
523-
break;
524-
case 'SECONDARY':
525-
readPreference = ReadPreference.SECONDARY;
526-
break;
527-
case 'SECONDARY_PREFERRED':
528-
readPreference = ReadPreference.SECONDARY_PREFERRED;
529-
break;
530-
case 'NEAREST':
531-
readPreference = ReadPreference.NEAREST;
532-
break;
533-
default:
534-
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Not supported read preference.');
535-
}
533+
switch (readPreference) {
534+
case 'PRIMARY':
535+
readPreference = ReadPreference.PRIMARY;
536+
break;
537+
case 'PRIMARY_PREFERRED':
538+
readPreference = ReadPreference.PRIMARY_PREFERRED;
539+
break;
540+
case 'SECONDARY':
541+
readPreference = ReadPreference.SECONDARY;
542+
break;
543+
case 'SECONDARY_PREFERRED':
544+
readPreference = ReadPreference.SECONDARY_PREFERRED;
545+
break;
546+
case 'NEAREST':
547+
readPreference = ReadPreference.NEAREST;
548+
break;
549+
case undefined:
550+
// this is to match existing tests, which were failing as [email protected] don't report readPreference anymore
551+
readPreference = ReadPreference.PRIMARY;
552+
break;
553+
default:
554+
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Not supported read preference.');
536555
}
537556
return readPreference;
538557
}

src/Adapters/Storage/Mongo/MongoTransform.js

+14-9
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,18 @@ const nestedMongoObjectToNestedParseObject = mongoObject => {
10141014
}
10151015
}
10161016

1017+
const transformPointerString = (schema, field, pointerString) => {
1018+
const objData = pointerString.split('$');
1019+
if (objData[0] !== schema.fields[field].targetClass) {
1020+
throw 'pointer to incorrect className';
1021+
}
1022+
return {
1023+
__type: 'Pointer',
1024+
className: objData[0],
1025+
objectId: objData[1]
1026+
};
1027+
}
1028+
10171029
// Converts from a mongo-format object to a REST-format object.
10181030
// Does not strip out anything based on a lack of authentication.
10191031
const mongoObjectToParseObject = (className, mongoObject, schema) => {
@@ -1126,15 +1138,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
11261138
if (mongoObject[key] === null) {
11271139
break;
11281140
}
1129-
var objData = mongoObject[key].split('$');
1130-
if (objData[0] !== schema.fields[newKey].targetClass) {
1131-
throw 'pointer to incorrect className';
1132-
}
1133-
restObject[newKey] = {
1134-
__type: 'Pointer',
1135-
className: objData[0],
1136-
objectId: objData[1]
1137-
};
1141+
restObject[newKey] = transformPointerString(schema, newKey, mongoObject[key]);
11381142
break;
11391143
} else if (key[0] == '_' && key != '__type') {
11401144
throw ('bad key in untransform: ' + key);
@@ -1345,4 +1349,5 @@ module.exports = {
13451349
mongoObjectToParseObject,
13461350
relativeTimeToDate,
13471351
transformConstraint,
1352+
transformPointerString,
13481353
};

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

+23-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import sql from './sql';
99
const PostgresRelationDoesNotExistError = '42P01';
1010
const PostgresDuplicateRelationError = '42P07';
1111
const PostgresDuplicateColumnError = '42701';
12+
const PostgresMissingColumnError = '42703';
1213
const PostgresDuplicateObjectError = '42710';
1314
const PostgresUniqueIndexViolationError = '23505';
1415
const PostgresTransactionAbortedError = '25P02';
@@ -1446,21 +1447,37 @@ export class PostgresStorageAdapter implements StorageAdapter {
14461447
const isArrayField = schema.fields
14471448
&& schema.fields[fieldName]
14481449
&& schema.fields[fieldName].type === 'Array';
1450+
const isPointerField = schema.fields
1451+
&& schema.fields[fieldName]
1452+
&& schema.fields[fieldName].type === 'Pointer';
14491453
const values = [field, column, className];
14501454
const where = buildWhereClause({ schema, query, index: 4 });
14511455
values.push(...where.values);
14521456

14531457
const wherePattern = where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
1454-
let qs = `SELECT DISTINCT ON ($1:raw) $2:raw FROM $3:name ${wherePattern}`;
1455-
if (isArrayField) {
1456-
qs = `SELECT distinct jsonb_array_elements($1:raw) as $2:raw FROM $3:name ${wherePattern}`;
1457-
}
1458+
const transformer = isArrayField ? 'jsonb_array_elements' : 'ON';
1459+
const qs = `SELECT DISTINCT ${transformer}($1:raw) $2:raw FROM $3:name ${wherePattern}`;
14581460
debug(qs, values);
14591461
return this._client.any(qs, values)
1460-
.catch(() => [])
1462+
.catch((error) => {
1463+
if (error.code === PostgresMissingColumnError) {
1464+
return [];
1465+
}
1466+
throw error;
1467+
})
14611468
.then((results) => {
14621469
if (fieldName.indexOf('.') === -1) {
1463-
return results.map(object => object[field]);
1470+
results = results.filter((object) => object[field] !== null);
1471+
return results.map(object => {
1472+
if (!isPointerField) {
1473+
return object[field];
1474+
}
1475+
return {
1476+
__type: 'Pointer',
1477+
className: schema.fields[fieldName].targetClass,
1478+
objectId: object[field]
1479+
};
1480+
});
14641481
}
14651482
const child = fieldName.split('.')[1];
14661483
return results.map(object => object[column][child]);

0 commit comments

Comments
 (0)