Support pointer in distinct query (#4471)
* Support pointer in distinct query * extract transform pointer string
This commit is contained in:
@@ -392,6 +392,23 @@ describe('Parse.Query Aggregate testing', () => {
|
|||||||
}).catch(done.fail);
|
}).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) => {
|
it('distinct class does not exist return empty', (done) => {
|
||||||
const options = Object.assign({}, masterKeyOptions, {
|
const options = Object.assign({}, masterKeyOptions, {
|
||||||
body: { distinct: 'unknown' }
|
body: { distinct: 'unknown' }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
transformKey,
|
transformKey,
|
||||||
transformWhere,
|
transformWhere,
|
||||||
transformUpdate,
|
transformUpdate,
|
||||||
|
transformPointerString,
|
||||||
} from './MongoTransform';
|
} from './MongoTransform';
|
||||||
import Parse from 'parse/node';
|
import Parse from 'parse/node';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -483,9 +484,19 @@ export class MongoStorageAdapter {
|
|||||||
|
|
||||||
distinct(className, schema, query, fieldName) {
|
distinct(className, schema, query, fieldName) {
|
||||||
schema = convertParseSchemaToMongoSchema(schema);
|
schema = convertParseSchemaToMongoSchema(schema);
|
||||||
|
const isPointerField = schema.fields[fieldName] && schema.fields[fieldName].type === 'Pointer';
|
||||||
|
if (isPointerField) {
|
||||||
|
fieldName = `_p_${fieldName}`
|
||||||
|
}
|
||||||
return this._adaptiveCollection(className)
|
return this._adaptiveCollection(className)
|
||||||
.then(collection => collection.distinct(fieldName, transformWhere(className, query, schema)))
|
.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) {
|
||||||
|
const field = fieldName.substring(3);
|
||||||
|
return transformPointerString(schema, field, object);
|
||||||
|
}
|
||||||
|
return mongoObjectToParseObject(className, object, schema);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregate(className, schema, pipeline, readPreference) {
|
aggregate(className, schema, pipeline, readPreference) {
|
||||||
|
|||||||
@@ -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.
|
// Converts from a mongo-format object to a REST-format object.
|
||||||
// Does not strip out anything based on a lack of authentication.
|
// Does not strip out anything based on a lack of authentication.
|
||||||
const mongoObjectToParseObject = (className, mongoObject, schema) => {
|
const mongoObjectToParseObject = (className, mongoObject, schema) => {
|
||||||
@@ -1126,15 +1138,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
|
|||||||
if (mongoObject[key] === null) {
|
if (mongoObject[key] === null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var objData = mongoObject[key].split('$');
|
restObject[newKey] = transformPointerString(schema, newKey, mongoObject[key]);
|
||||||
if (objData[0] !== schema.fields[newKey].targetClass) {
|
|
||||||
throw 'pointer to incorrect className';
|
|
||||||
}
|
|
||||||
restObject[newKey] = {
|
|
||||||
__type: 'Pointer',
|
|
||||||
className: objData[0],
|
|
||||||
objectId: objData[1]
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
} else if (key[0] == '_' && key != '__type') {
|
} else if (key[0] == '_' && key != '__type') {
|
||||||
throw ('bad key in untransform: ' + key);
|
throw ('bad key in untransform: ' + key);
|
||||||
@@ -1345,4 +1349,5 @@ module.exports = {
|
|||||||
mongoObjectToParseObject,
|
mongoObjectToParseObject,
|
||||||
relativeTimeToDate,
|
relativeTimeToDate,
|
||||||
transformConstraint,
|
transformConstraint,
|
||||||
|
transformPointerString,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import sql from './sql';
|
|||||||
const PostgresRelationDoesNotExistError = '42P01';
|
const PostgresRelationDoesNotExistError = '42P01';
|
||||||
const PostgresDuplicateRelationError = '42P07';
|
const PostgresDuplicateRelationError = '42P07';
|
||||||
const PostgresDuplicateColumnError = '42701';
|
const PostgresDuplicateColumnError = '42701';
|
||||||
|
const PostgresMissingColumnError = '42703';
|
||||||
const PostgresDuplicateObjectError = '42710';
|
const PostgresDuplicateObjectError = '42710';
|
||||||
const PostgresUniqueIndexViolationError = '23505';
|
const PostgresUniqueIndexViolationError = '23505';
|
||||||
const PostgresTransactionAbortedError = '25P02';
|
const PostgresTransactionAbortedError = '25P02';
|
||||||
@@ -1438,21 +1439,37 @@ export class PostgresStorageAdapter {
|
|||||||
const isArrayField = schema.fields
|
const isArrayField = schema.fields
|
||||||
&& schema.fields[fieldName]
|
&& schema.fields[fieldName]
|
||||||
&& schema.fields[fieldName].type === 'Array';
|
&& schema.fields[fieldName].type === 'Array';
|
||||||
|
const isPointerField = schema.fields
|
||||||
|
&& schema.fields[fieldName]
|
||||||
|
&& schema.fields[fieldName].type === 'Pointer';
|
||||||
const values = [field, column, className];
|
const values = [field, column, className];
|
||||||
const where = buildWhereClause({ schema, query, index: 4 });
|
const where = buildWhereClause({ schema, query, index: 4 });
|
||||||
values.push(...where.values);
|
values.push(...where.values);
|
||||||
|
|
||||||
const wherePattern = where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
|
const wherePattern = where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
|
||||||
let qs = `SELECT DISTINCT ON ($1:raw) $2:raw FROM $3:name ${wherePattern}`;
|
const transformer = isArrayField ? 'jsonb_array_elements' : 'ON';
|
||||||
if (isArrayField) {
|
const qs = `SELECT DISTINCT ${transformer}($1:raw) $2:raw FROM $3:name ${wherePattern}`;
|
||||||
qs = `SELECT distinct jsonb_array_elements($1:raw) as $2:raw FROM $3:name ${wherePattern}`;
|
|
||||||
}
|
|
||||||
debug(qs, values);
|
debug(qs, values);
|
||||||
return this._client.any(qs, values)
|
return this._client.any(qs, values)
|
||||||
.catch(() => [])
|
.catch((error) => {
|
||||||
|
if (error.code === PostgresMissingColumnError) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
if (fieldName.indexOf('.') === -1) {
|
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];
|
const child = fieldName.split('.')[1];
|
||||||
return results.map(object => object[column][child]);
|
return results.map(object => object[column][child]);
|
||||||
|
|||||||
Reference in New Issue
Block a user