From 29886971b958a54d2dd7a44c829cf85167e54262 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 29 Dec 2017 12:56:44 -0600 Subject: [PATCH 1/2] Support pointer in distinct query --- spec/ParseQuery.Aggregate.spec.js | 17 +++++++++++++++++ .../Storage/Mongo/MongoStorageAdapter.js | 16 +++++++++++++++- .../Storage/Postgres/PostgresStorageAdapter.js | 15 ++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/spec/ParseQuery.Aggregate.spec.js b/spec/ParseQuery.Aggregate.spec.js index c47f53ab5d..5987399a42 100644 --- a/spec/ParseQuery.Aggregate.spec.js +++ b/spec/ParseQuery.Aggregate.spec.js @@ -392,6 +392,23 @@ describe('Parse.Query Aggregate testing', () => { }).catch(done.fail); }); + it('distinct pointer', (done) => { + const pointer1 = new TestObject(); + const pointer2 = new TestObject(); + const obj1 = new TestObject({ pointer: pointer1 }); + const obj2 = new TestObject({ pointer: pointer2 }); + const obj3 = new TestObject({ pointer: pointer1 }); + Parse.Object.saveAll([pointer1, pointer2, obj1, obj2, obj3]).then(() => { + const query = new Parse.Query(TestObject); + return query.distinct('pointer'); + }).then((results) => { + expect(results.length).toEqual(2); + expect(results.some(result => result.objectId === pointer1.id)).toEqual(true); + expect(results.some(result => result.objectId === pointer2.id)).toEqual(true); + done(); + }); + }); + it('distinct class does not exist return empty', (done) => { const options = Object.assign({}, masterKeyOptions, { body: { distinct: 'unknown' } diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 5158e58c14..7203f2e2e2 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -483,9 +483,23 @@ export class MongoStorageAdapter { distinct(className, schema, query, fieldName) { schema = convertParseSchemaToMongoSchema(schema); + const isPointerField = schema.fields[fieldName] && schema.fields[fieldName].type === 'Pointer'; + if (isPointerField) { + fieldName = `_p_${fieldName}` + } return this._adaptiveCollection(className) .then(collection => collection.distinct(fieldName, transformWhere(className, query, schema))) - .then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema))); + .then(objects => objects.map(object => { + if (!isPointerField) { + return mongoObjectToParseObject(className, object, schema); + } + const objData = object.split('$'); + return { + __type: 'Pointer', + className: objData[0], + objectId: objData[1] + }; + })); } aggregate(className, schema, pipeline, readPreference) { diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index c49a6cc551..4e54b5e404 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1438,6 +1438,9 @@ export class PostgresStorageAdapter { const isArrayField = schema.fields && schema.fields[fieldName] && schema.fields[fieldName].type === 'Array'; + const isPointerField = schema.fields + && schema.fields[fieldName] + && schema.fields[fieldName].type === 'Pointer'; const values = [field, column, className]; const where = buildWhereClause({ schema, query, index: 4 }); values.push(...where.values); @@ -1452,7 +1455,17 @@ export class PostgresStorageAdapter { .catch(() => []) .then((results) => { if (fieldName.indexOf('.') === -1) { - return results.map(object => object[field]); + results = results.filter((object) => object[field] !== null); + return results.map(object => { + if (!isPointerField) { + return object[field]; + } + return { + __type: 'Pointer', + className: schema.fields[fieldName].targetClass, + objectId: object[field] + }; + }); } const child = fieldName.split('.')[1]; return results.map(object => object[column][child]); From 4dc103a398b971e7b80ecd5cb85d6fb05e90065a Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 29 Dec 2017 13:55:02 -0600 Subject: [PATCH 2/2] extract transform pointer string --- .../Storage/Mongo/MongoStorageAdapter.js | 13 ++++------- src/Adapters/Storage/Mongo/MongoTransform.js | 23 +++++++++++-------- .../Postgres/PostgresStorageAdapter.js | 14 +++++++---- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 7203f2e2e2..7871fcc8f0 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -10,6 +10,7 @@ import { transformKey, transformWhere, transformUpdate, + transformPointerString, } from './MongoTransform'; import Parse from 'parse/node'; import _ from 'lodash'; @@ -490,15 +491,11 @@ export class MongoStorageAdapter { return this._adaptiveCollection(className) .then(collection => collection.distinct(fieldName, transformWhere(className, query, schema))) .then(objects => objects.map(object => { - if (!isPointerField) { - return mongoObjectToParseObject(className, object, schema); + if (isPointerField) { + const field = fieldName.substring(3); + return transformPointerString(schema, field, object); } - const objData = object.split('$'); - return { - __type: 'Pointer', - className: objData[0], - objectId: objData[1] - }; + return mongoObjectToParseObject(className, object, schema); })); } diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 01c1aa1a8a..1e98eb1057 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -1014,6 +1014,18 @@ const nestedMongoObjectToNestedParseObject = mongoObject => { } } +const transformPointerString = (schema, field, pointerString) => { + const objData = pointerString.split('$'); + if (objData[0] !== schema.fields[field].targetClass) { + throw 'pointer to incorrect className'; + } + return { + __type: 'Pointer', + className: objData[0], + objectId: objData[1] + }; +} + // Converts from a mongo-format object to a REST-format object. // Does not strip out anything based on a lack of authentication. const mongoObjectToParseObject = (className, mongoObject, schema) => { @@ -1126,15 +1138,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => { if (mongoObject[key] === null) { break; } - var objData = mongoObject[key].split('$'); - if (objData[0] !== schema.fields[newKey].targetClass) { - throw 'pointer to incorrect className'; - } - restObject[newKey] = { - __type: 'Pointer', - className: objData[0], - objectId: objData[1] - }; + restObject[newKey] = transformPointerString(schema, newKey, mongoObject[key]); break; } else if (key[0] == '_' && key != '__type') { throw ('bad key in untransform: ' + key); @@ -1345,4 +1349,5 @@ module.exports = { mongoObjectToParseObject, relativeTimeToDate, transformConstraint, + transformPointerString, }; diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 4e54b5e404..8d830c6bd4 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -6,6 +6,7 @@ import sql from './sql'; const PostgresRelationDoesNotExistError = '42P01'; const PostgresDuplicateRelationError = '42P07'; const PostgresDuplicateColumnError = '42701'; +const PostgresMissingColumnError = '42703'; const PostgresDuplicateObjectError = '42710'; const PostgresUniqueIndexViolationError = '23505'; const PostgresTransactionAbortedError = '25P02'; @@ -1446,13 +1447,16 @@ export class PostgresStorageAdapter { values.push(...where.values); const wherePattern = where.pattern.length > 0 ? `WHERE ${where.pattern}` : ''; - let qs = `SELECT DISTINCT ON ($1:raw) $2:raw FROM $3:name ${wherePattern}`; - if (isArrayField) { - qs = `SELECT distinct jsonb_array_elements($1:raw) as $2:raw FROM $3:name ${wherePattern}`; - } + const transformer = isArrayField ? 'jsonb_array_elements' : 'ON'; + const qs = `SELECT DISTINCT ${transformer}($1:raw) $2:raw FROM $3:name ${wherePattern}`; debug(qs, values); return this._client.any(qs, values) - .catch(() => []) + .catch((error) => { + if (error.code === PostgresMissingColumnError) { + return []; + } + throw error; + }) .then((results) => { if (fieldName.indexOf('.') === -1) { results = results.filter((object) => object[field] !== null);