GraphQL: DX Relational Where Query (#6255)

* DX Relational Where Query

* Remove WherePointer & fix tests

* Add have, haveNot, exists on Pointer/Relation where input

* Merge branch 'master' into gql-relational-query

* Enable inQueryKey

* better descrption
This commit is contained in:
Antoine Cormouls
2019-12-05 19:14:16 +01:00
committed by Antonio Davi Macedo Coelho de Castro
parent afe49cb1f7
commit 5d76b2f354
7 changed files with 519 additions and 160 deletions

View File

@@ -4915,7 +4915,11 @@ describe('ParseGraphQLServer', () => {
OR: [ OR: [
{ {
pointerToUser: { pointerToUser: {
equalTo: user5.id, have: {
objectId: {
equalTo: user5.id,
},
},
}, },
}, },
{ {
@@ -4960,7 +4964,11 @@ describe('ParseGraphQLServer', () => {
variables: { variables: {
where: { where: {
pointerToUser: { pointerToUser: {
in: [user5.id], have: {
objectId: {
in: [user5.id],
},
},
}, },
}, },
}, },
@@ -5063,6 +5071,72 @@ describe('ParseGraphQLServer', () => {
} }
}); });
it('should support in query key', async () => {
try {
const country = new Parse.Object('Country');
country.set('code', 'FR');
await country.save();
const country2 = new Parse.Object('Country');
country2.set('code', 'US');
await country2.save();
const city = new Parse.Object('City');
city.set('country', 'FR');
city.set('name', 'city1');
await city.save();
const city2 = new Parse.Object('City');
city2.set('country', 'US');
city2.set('name', 'city2');
await city2.save();
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
const {
data: {
cities: { edges: result },
},
} = await apolloClient.query({
query: gql`
query inQueryKey($where: CityWhereInput) {
cities(where: $where) {
edges {
node {
country
name
}
}
}
}
`,
context: {
headers: {
'X-Parse-Master-Key': 'test',
},
},
variables: {
where: {
country: {
inQueryKey: {
query: {
className: 'Country',
where: { code: { equalTo: 'US' } },
},
key: 'code',
},
},
},
},
});
expect(result.length).toEqual(1);
expect(result[0].node.name).toEqual('city2');
} catch (e) {
handleError(e);
}
});
it('should support order, skip and first arguments', async () => { it('should support order, skip and first arguments', async () => {
const promises = []; const promises = [];
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
@@ -5278,7 +5352,11 @@ describe('ParseGraphQLServer', () => {
OR: [ OR: [
{ {
pointerToUser: { pointerToUser: {
equalTo: user5.id, have: {
objectId: {
equalTo: user5.id,
},
},
}, },
}, },
{ {
@@ -5332,7 +5410,11 @@ describe('ParseGraphQLServer', () => {
OR: [ OR: [
{ {
pointerToUser: { pointerToUser: {
equalTo: user5.id, have: {
objectId: {
equalTo: user5.id,
},
},
}, },
}, },
{ {
@@ -5746,7 +5828,11 @@ describe('ParseGraphQLServer', () => {
variables: { variables: {
where: { where: {
pointerToUser: { pointerToUser: {
inQuery: { where: {}, className: '_User' }, have: {
objectId: {
equalTo: 'xxxx',
},
},
}, },
}, },
}, },
@@ -8557,6 +8643,236 @@ describe('ParseGraphQLServer', () => {
expect(result2.companies.edges[0].node.objectId).toEqual(company1.id); expect(result2.companies.edges[0].node.objectId).toEqual(company1.id);
}); });
it_only_db('mongo')(
'should support relational where query',
async () => {
const president = new Parse.Object('President');
president.set('name', 'James');
await president.save();
const employee = new Parse.Object('Employee');
employee.set('name', 'John');
await employee.save();
const company1 = new Parse.Object('Company');
company1.set('name', 'imACompany1');
await company1.save();
const company2 = new Parse.Object('Company');
company2.set('name', 'imACompany2');
company2.relation('employees').add([employee]);
await company2.save();
const country = new Parse.Object('Country');
country.set('name', 'imACountry');
country.relation('companies').add([company1, company2]);
await country.save();
const country2 = new Parse.Object('Country');
country2.set('name', 'imACountry2');
country2.relation('companies').add([company1]);
await country2.save();
const country3 = new Parse.Object('Country');
country3.set('name', 'imACountry3');
country3.set('president', president);
await country3.save();
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
let {
data: {
countries: { edges: result },
},
} = await apolloClient.query({
query: gql`
query findCountry($where: CountryWhereInput) {
countries(where: $where) {
edges {
node {
id
objectId
companies {
edges {
node {
id
objectId
name
}
}
}
}
}
}
}
`,
variables: {
where: {
companies: {
have: {
employees: { have: { name: { equalTo: 'John' } } },
},
},
},
},
});
expect(result.length).toEqual(1);
result = result[0].node;
expect(result.objectId).toEqual(country.id);
expect(result.companies.edges.length).toEqual(2);
const {
data: {
countries: { edges: result2 },
},
} = await apolloClient.query({
query: gql`
query findCountry($where: CountryWhereInput) {
countries(where: $where) {
edges {
node {
id
objectId
companies {
edges {
node {
id
objectId
name
}
}
}
}
}
}
}
`,
variables: {
where: {
companies: {
have: {
OR: [
{ name: { equalTo: 'imACompany1' } },
{ name: { equalTo: 'imACompany2' } },
],
},
},
},
},
});
expect(result2.length).toEqual(2);
const {
data: {
countries: { edges: result3 },
},
} = await apolloClient.query({
query: gql`
query findCountry($where: CountryWhereInput) {
countries(where: $where) {
edges {
node {
id
name
}
}
}
}
`,
variables: {
where: {
companies: { exists: false },
},
},
});
expect(result3.length).toEqual(1);
expect(result3[0].node.name).toEqual('imACountry3');
const {
data: {
countries: { edges: result4 },
},
} = await apolloClient.query({
query: gql`
query findCountry($where: CountryWhereInput) {
countries(where: $where) {
edges {
node {
id
name
}
}
}
}
`,
variables: {
where: {
president: { exists: false },
},
},
});
expect(result4.length).toEqual(2);
const {
data: {
countries: { edges: result5 },
},
} = await apolloClient.query({
query: gql`
query findCountry($where: CountryWhereInput) {
countries(where: $where) {
edges {
node {
id
name
}
}
}
}
`,
variables: {
where: {
president: { exists: true },
},
},
});
expect(result5.length).toEqual(1);
const {
data: {
countries: { edges: result6 },
},
} = await apolloClient.query({
query: gql`
query findCountry($where: CountryWhereInput) {
countries(where: $where) {
edges {
node {
id
objectId
name
}
}
}
}
`,
variables: {
where: {
companies: {
haveNot: {
OR: [
{ name: { equalTo: 'imACompany1' } },
{ name: { equalTo: 'imACompany2' } },
],
},
},
},
},
});
expect(result6.length).toEqual(1);
expect(result6.length).toEqual(1);
expect(result6[0].node.name).toEqual('imACountry3');
}
);
it('should support files', async () => { it('should support files', async () => {
try { try {
parseServer = await global.reconfigureServer({ parseServer = await global.reconfigureServer({

View File

@@ -67,13 +67,12 @@ const findObjects = async (
auth, auth,
info, info,
selectedFields, selectedFields,
fields parseClasses
) => { ) => {
if (!where) { if (!where) {
where = {}; where = {};
} }
transformQueryInputToParse(where, fields, className); transformQueryInputToParse(where, className, parseClasses);
const skipAndLimitCalculation = calculateSkipAndLimit( const skipAndLimitCalculation = calculateSkipAndLimit(
skipInput, skipInput,
first, first,

View File

@@ -713,35 +713,6 @@ const COUNT_ATT = {
type: new GraphQLNonNull(GraphQLInt), type: new GraphQLNonNull(GraphQLInt),
}; };
const SUBQUERY_INPUT = new GraphQLInputObjectType({
name: 'SubqueryInput',
description:
'The SubqueryInput type is used to specify a sub query to another class.',
fields: {
className: CLASS_NAME_ATT,
where: Object.assign({}, WHERE_ATT, {
type: new GraphQLNonNull(WHERE_ATT.type),
}),
},
});
const SELECT_INPUT = new GraphQLInputObjectType({
name: 'SelectInput',
description:
'The SelectInput type is used to specify an inQueryKey or a notInQueryKey operation on a constraint.',
fields: {
query: {
description: 'This is the subquery to be executed.',
type: new GraphQLNonNull(SUBQUERY_INPUT),
},
key: {
description:
'This is the key in the result of the subquery that must match (not match) the field.',
type: new GraphQLNonNull(GraphQLString),
},
},
});
const SEARCH_INPUT = new GraphQLInputObjectType({ const SEARCH_INPUT = new GraphQLInputObjectType({
name: 'SearchInput', name: 'SearchInput',
description: description:
@@ -907,18 +878,6 @@ const exists = {
type: GraphQLBoolean, type: GraphQLBoolean,
}; };
const inQueryKey = {
description:
'This is the inQueryKey operator to specify a constraint to select the objects where a field equals to a key in the result of a different query.',
type: SELECT_INPUT,
};
const notInQueryKey = {
description:
'This is the notInQueryKey operator to specify a constraint to select the objects where a field do not equal to a key in the result of a different query.',
type: SELECT_INPUT,
};
const matchesRegex = { const matchesRegex = {
description: description:
'This is the matchesRegex operator to specify a constraint to select the objects where the value of a field matches a specified regular expression.', 'This is the matchesRegex operator to specify a constraint to select the objects where the value of a field matches a specified regular expression.',
@@ -931,6 +890,47 @@ const options = {
type: GraphQLString, type: GraphQLString,
}; };
const SUBQUERY_INPUT = new GraphQLInputObjectType({
name: 'SubqueryInput',
description:
'The SubqueryInput type is used to specify a sub query to another class.',
fields: {
className: CLASS_NAME_ATT,
where: Object.assign({}, WHERE_ATT, {
type: new GraphQLNonNull(WHERE_ATT.type),
}),
},
});
const SELECT_INPUT = new GraphQLInputObjectType({
name: 'SelectInput',
description:
'The SelectInput type is used to specify an inQueryKey or a notInQueryKey operation on a constraint.',
fields: {
query: {
description: 'This is the subquery to be executed.',
type: new GraphQLNonNull(SUBQUERY_INPUT),
},
key: {
description:
'This is the key in the result of the subquery that must match (not match) the field.',
type: new GraphQLNonNull(GraphQLString),
},
},
});
const inQueryKey = {
description:
'This is the inQueryKey operator to specify a constraint to select the objects where a field equals to a key in the result of a different query.',
type: SELECT_INPUT,
};
const notInQueryKey = {
description:
'This is the notInQueryKey operator to specify a constraint to select the objects where a field do not equal to a key in the result of a different query.',
type: SELECT_INPUT,
};
const ID_WHERE_INPUT = new GraphQLInputObjectType({ const ID_WHERE_INPUT = new GraphQLInputObjectType({
name: 'IdWhereInput', name: 'IdWhereInput',
description: description:
@@ -964,8 +964,6 @@ const STRING_WHERE_INPUT = new GraphQLInputObjectType({
in: inOp(GraphQLString), in: inOp(GraphQLString),
notIn: notIn(GraphQLString), notIn: notIn(GraphQLString),
exists, exists,
inQueryKey,
notInQueryKey,
matchesRegex, matchesRegex,
options, options,
text: { text: {
@@ -973,6 +971,8 @@ const STRING_WHERE_INPUT = new GraphQLInputObjectType({
'This is the $text operator to specify a full text search constraint.', 'This is the $text operator to specify a full text search constraint.',
type: TEXT_INPUT, type: TEXT_INPUT,
}, },
inQueryKey,
notInQueryKey,
}, },
}); });
@@ -1022,8 +1022,6 @@ const ARRAY_WHERE_INPUT = new GraphQLInputObjectType({
in: inOp(ANY), in: inOp(ANY),
notIn: notIn(ANY), notIn: notIn(ANY),
exists, exists,
inQueryKey,
notInQueryKey,
containedBy: { containedBy: {
description: description:
'This is the containedBy operator to specify a constraint to select the objects where the values of an array field is contained by another specified array.', 'This is the containedBy operator to specify a constraint to select the objects where the values of an array field is contained by another specified array.',
@@ -1034,6 +1032,8 @@ const ARRAY_WHERE_INPUT = new GraphQLInputObjectType({
'This is the contains operator to specify a constraint to select the objects where the values of an array field contain all elements of another specified array.', 'This is the contains operator to specify a constraint to select the objects where the values of an array field contain all elements of another specified array.',
type: new GraphQLList(ANY), type: new GraphQLList(ANY),
}, },
inQueryKey,
notInQueryKey,
}, },
}); });
@@ -1123,10 +1123,10 @@ const FILE_WHERE_INPUT = new GraphQLInputObjectType({
in: inOp(FILE), in: inOp(FILE),
notIn: notIn(FILE), notIn: notIn(FILE),
exists, exists,
inQueryKey,
notInQueryKey,
matchesRegex, matchesRegex,
options, options,
inQueryKey,
notInQueryKey,
}, },
}); });
@@ -1249,8 +1249,6 @@ const load = parseGraphQLSchema => {
parseGraphQLSchema.addGraphQLType(PARSE_OBJECT, true); parseGraphQLSchema.addGraphQLType(PARSE_OBJECT, true);
parseGraphQLSchema.addGraphQLType(READ_PREFERENCE, true); parseGraphQLSchema.addGraphQLType(READ_PREFERENCE, true);
parseGraphQLSchema.addGraphQLType(READ_OPTIONS_INPUT, true); parseGraphQLSchema.addGraphQLType(READ_OPTIONS_INPUT, true);
parseGraphQLSchema.addGraphQLType(SUBQUERY_INPUT, true);
parseGraphQLSchema.addGraphQLType(SELECT_INPUT, true);
parseGraphQLSchema.addGraphQLType(SEARCH_INPUT, true); parseGraphQLSchema.addGraphQLType(SEARCH_INPUT, true);
parseGraphQLSchema.addGraphQLType(TEXT_INPUT, true); parseGraphQLSchema.addGraphQLType(TEXT_INPUT, true);
parseGraphQLSchema.addGraphQLType(BOX_INPUT, true); parseGraphQLSchema.addGraphQLType(BOX_INPUT, true);
@@ -1279,6 +1277,8 @@ const load = parseGraphQLSchema => {
parseGraphQLSchema.addGraphQLType(USER_ACL, true); parseGraphQLSchema.addGraphQLType(USER_ACL, true);
parseGraphQLSchema.addGraphQLType(ROLE_ACL, true); parseGraphQLSchema.addGraphQLType(ROLE_ACL, true);
parseGraphQLSchema.addGraphQLType(PUBLIC_ACL, true); parseGraphQLSchema.addGraphQLType(PUBLIC_ACL, true);
parseGraphQLSchema.addGraphQLType(SUBQUERY_INPUT, true);
parseGraphQLSchema.addGraphQLType(SELECT_INPUT, true);
}; };
export { export {
@@ -1297,6 +1297,8 @@ export {
DATE, DATE,
BYTES, BYTES,
parseFileValue, parseFileValue,
SUBQUERY_INPUT,
SELECT_INPUT,
FILE, FILE,
FILE_INFO, FILE_INFO,
GEO_POINT_FIELDS, GEO_POINT_FIELDS,
@@ -1326,8 +1328,6 @@ export {
SKIP_ATT, SKIP_ATT,
LIMIT_ATT, LIMIT_ATT,
COUNT_ATT, COUNT_ATT,
SUBQUERY_INPUT,
SELECT_INPUT,
SEARCH_INPUT, SEARCH_INPUT,
TEXT_INPUT, TEXT_INPUT,
BOX_INPUT, BOX_INPUT,
@@ -1344,10 +1344,10 @@ export {
inOp, inOp,
notIn, notIn,
exists, exists,
inQueryKey,
notInQueryKey,
matchesRegex, matchesRegex,
options, options,
inQueryKey,
notInQueryKey,
ID_WHERE_INPUT, ID_WHERE_INPUT,
STRING_WHERE_INPUT, STRING_WHERE_INPUT,
NUMBER_WHERE_INPUT, NUMBER_WHERE_INPUT,

View File

@@ -145,7 +145,7 @@ const load = function(
auth, auth,
info, info,
selectedFields, selectedFields,
parseClass.fields parseGraphQLSchema.parseClasses
); );
} catch (e) { } catch (e) {
parseGraphQLSchema.handleError(e); parseGraphQLSchema.handleError(e);

View File

@@ -5,6 +5,7 @@ import {
GraphQLList, GraphQLList,
GraphQLInputObjectType, GraphQLInputObjectType,
GraphQLNonNull, GraphQLNonNull,
GraphQLBoolean,
GraphQLEnumType, GraphQLEnumType,
} from 'graphql'; } from 'graphql';
import { import {
@@ -262,34 +263,6 @@ const load = (
parseGraphQLSchema.addGraphQLType(classGraphQLRelationType) || parseGraphQLSchema.addGraphQLType(classGraphQLRelationType) ||
defaultGraphQLTypes.OBJECT; defaultGraphQLTypes.OBJECT;
const classGraphQLConstraintTypeName = `${graphQLClassName}PointerWhereInput`;
let classGraphQLConstraintType = new GraphQLInputObjectType({
name: classGraphQLConstraintTypeName,
description: `The ${classGraphQLConstraintTypeName} input type is used in operations that involve filtering objects by a pointer field to ${graphQLClassName} class.`,
fields: {
equalTo: defaultGraphQLTypes.equalTo(GraphQLID),
notEqualTo: defaultGraphQLTypes.notEqualTo(GraphQLID),
in: defaultGraphQLTypes.inOp(defaultGraphQLTypes.OBJECT_ID),
notIn: defaultGraphQLTypes.notIn(defaultGraphQLTypes.OBJECT_ID),
exists: defaultGraphQLTypes.exists,
inQueryKey: defaultGraphQLTypes.inQueryKey,
notInQueryKey: defaultGraphQLTypes.notInQueryKey,
inQuery: {
description:
'This is the inQuery operator to specify a constraint to select the objects where a field equals to any of the object ids in the result of a different query.',
type: defaultGraphQLTypes.SUBQUERY_INPUT,
},
notInQuery: {
description:
'This is the notInQuery operator to specify a constraint to select the objects where a field do not equal to any of the object ids in the result of a different query.',
type: defaultGraphQLTypes.SUBQUERY_INPUT,
},
},
});
classGraphQLConstraintType = parseGraphQLSchema.addGraphQLType(
classGraphQLConstraintType
);
const classGraphQLConstraintsTypeName = `${graphQLClassName}WhereInput`; const classGraphQLConstraintsTypeName = `${graphQLClassName}WhereInput`;
let classGraphQLConstraintsType = new GraphQLInputObjectType({ let classGraphQLConstraintsType = new GraphQLInputObjectType({
name: classGraphQLConstraintsTypeName, name: classGraphQLConstraintsTypeName,
@@ -339,6 +312,31 @@ const load = (
parseGraphQLSchema.addGraphQLType(classGraphQLConstraintsType) || parseGraphQLSchema.addGraphQLType(classGraphQLConstraintsType) ||
defaultGraphQLTypes.OBJECT; defaultGraphQLTypes.OBJECT;
const classGraphQLRelationConstraintsTypeName = `${graphQLClassName}RelationWhereInput`;
let classGraphQLRelationConstraintsType = new GraphQLInputObjectType({
name: classGraphQLRelationConstraintsTypeName,
description: `The ${classGraphQLRelationConstraintsTypeName} input type is used in operations that involve filtering objects of ${graphQLClassName} class.`,
fields: () => ({
have: {
description:
'Run a relational/pointer query where at least one child object can match.',
type: classGraphQLConstraintsType,
},
haveNot: {
description:
'Run an inverted relational/pointer query where at least one child object can match.',
type: classGraphQLConstraintsType,
},
exists: {
description: 'Check if the relation/pointer contains objects.',
type: GraphQLBoolean,
},
}),
});
classGraphQLRelationConstraintsType =
parseGraphQLSchema.addGraphQLType(classGraphQLRelationConstraintsType) ||
defaultGraphQLTypes.OBJECT;
const classGraphQLOrderTypeName = `${graphQLClassName}Order`; const classGraphQLOrderTypeName = `${graphQLClassName}Order`;
let classGraphQLOrderType = new GraphQLEnumType({ let classGraphQLOrderType = new GraphQLEnumType({
name: classGraphQLOrderTypeName, name: classGraphQLOrderTypeName,
@@ -464,10 +462,7 @@ const load = (
auth, auth,
info, info,
selectedFields, selectedFields,
parseGraphQLSchema.parseClasses.find( parseGraphQLSchema.parseClasses
parseClass =>
parseClass.className === source[field].className
).fields
); );
} catch (e) { } catch (e) {
parseGraphQLSchema.handleError(e); parseGraphQLSchema.handleError(e);
@@ -558,8 +553,8 @@ const load = (
classGraphQLRelationType, classGraphQLRelationType,
classGraphQLCreateType, classGraphQLCreateType,
classGraphQLUpdateType, classGraphQLUpdateType,
classGraphQLConstraintType,
classGraphQLConstraintsType, classGraphQLConstraintsType,
classGraphQLRelationConstraintsType,
classGraphQLFindArgs, classGraphQLFindArgs,
classGraphQLOutputType, classGraphQLOutputType,
classGraphQLFindResultType, classGraphQLFindResultType,

View File

@@ -26,9 +26,9 @@ const transformConstraintTypeToGraphQL = (
case 'Pointer': case 'Pointer':
if ( if (
parseClassTypes[targetClass] && parseClassTypes[targetClass] &&
parseClassTypes[targetClass].classGraphQLConstraintType parseClassTypes[targetClass].classGraphQLRelationConstraintsType
) { ) {
return parseClassTypes[targetClass].classGraphQLConstraintType; return parseClassTypes[targetClass].classGraphQLRelationConstraintsType;
} else { } else {
return defaultGraphQLTypes.OBJECT; return defaultGraphQLTypes.OBJECT;
} }
@@ -43,6 +43,14 @@ const transformConstraintTypeToGraphQL = (
case 'ACL': case 'ACL':
return defaultGraphQLTypes.OBJECT_WHERE_INPUT; return defaultGraphQLTypes.OBJECT_WHERE_INPUT;
case 'Relation': case 'Relation':
if (
parseClassTypes[targetClass] &&
parseClassTypes[targetClass].classGraphQLRelationConstraintsType
) {
return parseClassTypes[targetClass].classGraphQLRelationConstraintsType;
} else {
return defaultGraphQLTypes.OBJECT;
}
default: default:
return undefined; return undefined;
} }

View File

@@ -1,7 +1,6 @@
import { fromGlobalId } from 'graphql-relay'; import { fromGlobalId } from 'graphql-relay';
const parseQueryMap = { const parseQueryMap = {
id: 'objectId',
OR: '$or', OR: '$or',
AND: '$and', AND: '$and',
NOR: '$nor', NOR: '$nor',
@@ -47,13 +46,44 @@ const parseConstraintMap = {
const transformQueryConstraintInputToParse = ( const transformQueryConstraintInputToParse = (
constraints, constraints,
fields,
parentFieldName, parentFieldName,
parentConstraints className,
parentConstraints,
parseClasses
) => { ) => {
const fields = parseClasses.find(
parseClass => parseClass.className === className
).fields;
if (parentFieldName === 'id' && className) {
Object.keys(constraints).forEach(constraintName => {
const constraintValue = constraints[constraintName];
if (typeof constraintValue === 'string') {
const globalIdObject = fromGlobalId(constraintValue);
if (globalIdObject.type === className) {
constraints[constraintName] = globalIdObject.id;
}
} else if (Array.isArray(constraintValue)) {
constraints[constraintName] = constraintValue.map(value => {
const globalIdObject = fromGlobalId(value);
if (globalIdObject.type === className) {
return globalIdObject.id;
}
return value;
});
}
});
parentConstraints.objectId = constraints;
delete parentConstraints.id;
}
Object.keys(constraints).forEach(fieldName => { Object.keys(constraints).forEach(fieldName => {
let fieldValue = constraints[fieldName]; let fieldValue = constraints[fieldName];
if (parseConstraintMap[fieldName]) {
constraints[parseConstraintMap[fieldName]] = constraints[fieldName];
delete constraints[fieldName];
}
/** /**
* If we have a key-value pair, we need to change the way the constraint is structured. * If we have a key-value pair, we need to change the way the constraint is structured.
* *
@@ -91,30 +121,65 @@ const transformQueryConstraintInputToParse = (
...parentConstraints[`${parentFieldName}.${fieldValue.key}`], ...parentConstraints[`${parentFieldName}.${fieldValue.key}`],
[parseConstraintMap[fieldName]]: fieldValue.value, [parseConstraintMap[fieldName]]: fieldValue.value,
}; };
} else if (parseConstraintMap[fieldName]) { } else if (
delete constraints[fieldName]; fields[parentFieldName] &&
fieldName = parseConstraintMap[fieldName]; (fields[parentFieldName].type === 'Pointer' ||
constraints[fieldName] = fieldValue; fields[parentFieldName].type === 'Relation')
) {
// If parent field type is Pointer, changes constraint value to format expected const { targetClass } = fields[parentFieldName];
// by Parse. if (fieldName === 'exists') {
if ( if (fields[parentFieldName].type === 'Relation') {
fields[parentFieldName] && const whereTarget = fieldValue ? 'where' : 'notWhere';
fields[parentFieldName].type === 'Pointer' && if (constraints[whereTarget]) {
typeof fieldValue === 'string' if (constraints[whereTarget].objectId) {
) { constraints[whereTarget].objectId = {
const { targetClass } = fields[parentFieldName]; ...constraints[whereTarget].objectId,
let objectId = fieldValue; $exists: fieldValue,
const globalIdObject = fromGlobalId(objectId); };
if (globalIdObject.type === targetClass) { } else {
objectId = globalIdObject.id; constraints[whereTarget].objectId = {
$exists: fieldValue,
};
}
} else {
const parseWhereTarget = fieldValue ? '$inQuery' : '$notInQuery';
parentConstraints[parentFieldName][parseWhereTarget] = {
where: { objectId: { $exists: true } },
className: targetClass,
};
}
delete constraints.$exists;
} else {
parentConstraints[parentFieldName].$exists = fieldValue;
} }
constraints[fieldName] = { return;
__type: 'Pointer',
className: targetClass,
objectId,
};
} }
switch (fieldName) {
case 'have':
parentConstraints[parentFieldName].$inQuery = {
where: fieldValue,
className: targetClass,
};
transformQueryInputToParse(
parentConstraints[parentFieldName].$inQuery.where,
targetClass,
parseClasses
);
break;
case 'haveNot':
parentConstraints[parentFieldName].$notInQuery = {
where: fieldValue,
className: targetClass,
};
transformQueryInputToParse(
parentConstraints[parentFieldName].$notInQuery.where,
targetClass,
parseClasses
);
break;
}
delete constraints[fieldName];
return;
} }
switch (fieldName) { switch (fieldName) {
case '$point': case '$point':
@@ -170,20 +235,21 @@ const transformQueryConstraintInputToParse = (
} }
if (typeof fieldValue === 'object') { if (typeof fieldValue === 'object') {
if (fieldName === 'where') { if (fieldName === 'where') {
transformQueryInputToParse(fieldValue); transformQueryInputToParse(fieldValue, className, parseClasses);
} else { } else {
transformQueryConstraintInputToParse( transformQueryConstraintInputToParse(
fieldValue, fieldValue,
fields,
fieldName, fieldName,
constraints className,
constraints,
parseClasses
); );
} }
} }
}); });
}; };
const transformQueryInputToParse = (constraints, fields, className) => { const transformQueryInputToParse = (constraints, className, parseClasses) => {
if (!constraints || typeof constraints !== 'object') { if (!constraints || typeof constraints !== 'object') {
return; return;
} }
@@ -195,42 +261,17 @@ const transformQueryInputToParse = (constraints, fields, className) => {
delete constraints[fieldName]; delete constraints[fieldName];
fieldName = parseQueryMap[fieldName]; fieldName = parseQueryMap[fieldName];
constraints[fieldName] = fieldValue; constraints[fieldName] = fieldValue;
fieldValue.forEach(fieldValueItem => {
if (fieldName !== 'objectId') { transformQueryInputToParse(fieldValueItem, className, parseClasses);
fieldValue.forEach(fieldValueItem => { });
transformQueryInputToParse(fieldValueItem, fields, className); return;
}); } else {
return;
} else if (className) {
Object.keys(fieldValue).forEach(constraintName => {
const constraintValue = fieldValue[constraintName];
if (typeof constraintValue === 'string') {
const globalIdObject = fromGlobalId(constraintValue);
if (globalIdObject.type === className) {
fieldValue[constraintName] = globalIdObject.id;
}
} else if (Array.isArray(constraintValue)) {
fieldValue[constraintName] = constraintValue.map(value => {
const globalIdObject = fromGlobalId(value);
if (globalIdObject.type === className) {
return globalIdObject.id;
}
return value;
});
}
});
}
}
if (typeof fieldValue === 'object') {
transformQueryConstraintInputToParse( transformQueryConstraintInputToParse(
fieldValue, fieldValue,
fields,
fieldName, fieldName,
constraints className,
constraints,
parseClasses
); );
} }
}); });