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:
committed by
Antonio Davi Macedo Coelho de Castro
parent
afe49cb1f7
commit
5d76b2f354
@@ -4915,7 +4915,11 @@ describe('ParseGraphQLServer', () => {
|
||||
OR: [
|
||||
{
|
||||
pointerToUser: {
|
||||
equalTo: user5.id,
|
||||
have: {
|
||||
objectId: {
|
||||
equalTo: user5.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -4960,7 +4964,11 @@ describe('ParseGraphQLServer', () => {
|
||||
variables: {
|
||||
where: {
|
||||
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 () => {
|
||||
const promises = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
@@ -5278,7 +5352,11 @@ describe('ParseGraphQLServer', () => {
|
||||
OR: [
|
||||
{
|
||||
pointerToUser: {
|
||||
equalTo: user5.id,
|
||||
have: {
|
||||
objectId: {
|
||||
equalTo: user5.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -5332,7 +5410,11 @@ describe('ParseGraphQLServer', () => {
|
||||
OR: [
|
||||
{
|
||||
pointerToUser: {
|
||||
equalTo: user5.id,
|
||||
have: {
|
||||
objectId: {
|
||||
equalTo: user5.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -5746,7 +5828,11 @@ describe('ParseGraphQLServer', () => {
|
||||
variables: {
|
||||
where: {
|
||||
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);
|
||||
});
|
||||
|
||||
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 () => {
|
||||
try {
|
||||
parseServer = await global.reconfigureServer({
|
||||
|
||||
@@ -67,13 +67,12 @@ const findObjects = async (
|
||||
auth,
|
||||
info,
|
||||
selectedFields,
|
||||
fields
|
||||
parseClasses
|
||||
) => {
|
||||
if (!where) {
|
||||
where = {};
|
||||
}
|
||||
transformQueryInputToParse(where, fields, className);
|
||||
|
||||
transformQueryInputToParse(where, className, parseClasses);
|
||||
const skipAndLimitCalculation = calculateSkipAndLimit(
|
||||
skipInput,
|
||||
first,
|
||||
|
||||
@@ -713,35 +713,6 @@ const COUNT_ATT = {
|
||||
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({
|
||||
name: 'SearchInput',
|
||||
description:
|
||||
@@ -907,18 +878,6 @@ const exists = {
|
||||
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 = {
|
||||
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.',
|
||||
@@ -931,6 +890,47 @@ const options = {
|
||||
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({
|
||||
name: 'IdWhereInput',
|
||||
description:
|
||||
@@ -964,8 +964,6 @@ const STRING_WHERE_INPUT = new GraphQLInputObjectType({
|
||||
in: inOp(GraphQLString),
|
||||
notIn: notIn(GraphQLString),
|
||||
exists,
|
||||
inQueryKey,
|
||||
notInQueryKey,
|
||||
matchesRegex,
|
||||
options,
|
||||
text: {
|
||||
@@ -973,6 +971,8 @@ const STRING_WHERE_INPUT = new GraphQLInputObjectType({
|
||||
'This is the $text operator to specify a full text search constraint.',
|
||||
type: TEXT_INPUT,
|
||||
},
|
||||
inQueryKey,
|
||||
notInQueryKey,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1022,8 +1022,6 @@ const ARRAY_WHERE_INPUT = new GraphQLInputObjectType({
|
||||
in: inOp(ANY),
|
||||
notIn: notIn(ANY),
|
||||
exists,
|
||||
inQueryKey,
|
||||
notInQueryKey,
|
||||
containedBy: {
|
||||
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.',
|
||||
@@ -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.',
|
||||
type: new GraphQLList(ANY),
|
||||
},
|
||||
inQueryKey,
|
||||
notInQueryKey,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1123,10 +1123,10 @@ const FILE_WHERE_INPUT = new GraphQLInputObjectType({
|
||||
in: inOp(FILE),
|
||||
notIn: notIn(FILE),
|
||||
exists,
|
||||
inQueryKey,
|
||||
notInQueryKey,
|
||||
matchesRegex,
|
||||
options,
|
||||
inQueryKey,
|
||||
notInQueryKey,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1249,8 +1249,6 @@ const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.addGraphQLType(PARSE_OBJECT, true);
|
||||
parseGraphQLSchema.addGraphQLType(READ_PREFERENCE, true);
|
||||
parseGraphQLSchema.addGraphQLType(READ_OPTIONS_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(SUBQUERY_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(SELECT_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(SEARCH_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(TEXT_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(BOX_INPUT, true);
|
||||
@@ -1279,6 +1277,8 @@ const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.addGraphQLType(USER_ACL, true);
|
||||
parseGraphQLSchema.addGraphQLType(ROLE_ACL, true);
|
||||
parseGraphQLSchema.addGraphQLType(PUBLIC_ACL, true);
|
||||
parseGraphQLSchema.addGraphQLType(SUBQUERY_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(SELECT_INPUT, true);
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -1297,6 +1297,8 @@ export {
|
||||
DATE,
|
||||
BYTES,
|
||||
parseFileValue,
|
||||
SUBQUERY_INPUT,
|
||||
SELECT_INPUT,
|
||||
FILE,
|
||||
FILE_INFO,
|
||||
GEO_POINT_FIELDS,
|
||||
@@ -1326,8 +1328,6 @@ export {
|
||||
SKIP_ATT,
|
||||
LIMIT_ATT,
|
||||
COUNT_ATT,
|
||||
SUBQUERY_INPUT,
|
||||
SELECT_INPUT,
|
||||
SEARCH_INPUT,
|
||||
TEXT_INPUT,
|
||||
BOX_INPUT,
|
||||
@@ -1344,10 +1344,10 @@ export {
|
||||
inOp,
|
||||
notIn,
|
||||
exists,
|
||||
inQueryKey,
|
||||
notInQueryKey,
|
||||
matchesRegex,
|
||||
options,
|
||||
inQueryKey,
|
||||
notInQueryKey,
|
||||
ID_WHERE_INPUT,
|
||||
STRING_WHERE_INPUT,
|
||||
NUMBER_WHERE_INPUT,
|
||||
|
||||
@@ -145,7 +145,7 @@ const load = function(
|
||||
auth,
|
||||
info,
|
||||
selectedFields,
|
||||
parseClass.fields
|
||||
parseGraphQLSchema.parseClasses
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
GraphQLList,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLNonNull,
|
||||
GraphQLBoolean,
|
||||
GraphQLEnumType,
|
||||
} from 'graphql';
|
||||
import {
|
||||
@@ -262,34 +263,6 @@ const load = (
|
||||
parseGraphQLSchema.addGraphQLType(classGraphQLRelationType) ||
|
||||
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`;
|
||||
let classGraphQLConstraintsType = new GraphQLInputObjectType({
|
||||
name: classGraphQLConstraintsTypeName,
|
||||
@@ -339,6 +312,31 @@ const load = (
|
||||
parseGraphQLSchema.addGraphQLType(classGraphQLConstraintsType) ||
|
||||
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`;
|
||||
let classGraphQLOrderType = new GraphQLEnumType({
|
||||
name: classGraphQLOrderTypeName,
|
||||
@@ -464,10 +462,7 @@ const load = (
|
||||
auth,
|
||||
info,
|
||||
selectedFields,
|
||||
parseGraphQLSchema.parseClasses.find(
|
||||
parseClass =>
|
||||
parseClass.className === source[field].className
|
||||
).fields
|
||||
parseGraphQLSchema.parseClasses
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
@@ -558,8 +553,8 @@ const load = (
|
||||
classGraphQLRelationType,
|
||||
classGraphQLCreateType,
|
||||
classGraphQLUpdateType,
|
||||
classGraphQLConstraintType,
|
||||
classGraphQLConstraintsType,
|
||||
classGraphQLRelationConstraintsType,
|
||||
classGraphQLFindArgs,
|
||||
classGraphQLOutputType,
|
||||
classGraphQLFindResultType,
|
||||
|
||||
@@ -26,9 +26,9 @@ const transformConstraintTypeToGraphQL = (
|
||||
case 'Pointer':
|
||||
if (
|
||||
parseClassTypes[targetClass] &&
|
||||
parseClassTypes[targetClass].classGraphQLConstraintType
|
||||
parseClassTypes[targetClass].classGraphQLRelationConstraintsType
|
||||
) {
|
||||
return parseClassTypes[targetClass].classGraphQLConstraintType;
|
||||
return parseClassTypes[targetClass].classGraphQLRelationConstraintsType;
|
||||
} else {
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
}
|
||||
@@ -43,6 +43,14 @@ const transformConstraintTypeToGraphQL = (
|
||||
case 'ACL':
|
||||
return defaultGraphQLTypes.OBJECT_WHERE_INPUT;
|
||||
case 'Relation':
|
||||
if (
|
||||
parseClassTypes[targetClass] &&
|
||||
parseClassTypes[targetClass].classGraphQLRelationConstraintsType
|
||||
) {
|
||||
return parseClassTypes[targetClass].classGraphQLRelationConstraintsType;
|
||||
} else {
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { fromGlobalId } from 'graphql-relay';
|
||||
|
||||
const parseQueryMap = {
|
||||
id: 'objectId',
|
||||
OR: '$or',
|
||||
AND: '$and',
|
||||
NOR: '$nor',
|
||||
@@ -47,13 +46,44 @@ const parseConstraintMap = {
|
||||
|
||||
const transformQueryConstraintInputToParse = (
|
||||
constraints,
|
||||
fields,
|
||||
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 => {
|
||||
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.
|
||||
*
|
||||
@@ -91,30 +121,65 @@ const transformQueryConstraintInputToParse = (
|
||||
...parentConstraints[`${parentFieldName}.${fieldValue.key}`],
|
||||
[parseConstraintMap[fieldName]]: fieldValue.value,
|
||||
};
|
||||
} else if (parseConstraintMap[fieldName]) {
|
||||
delete constraints[fieldName];
|
||||
fieldName = parseConstraintMap[fieldName];
|
||||
constraints[fieldName] = fieldValue;
|
||||
|
||||
// If parent field type is Pointer, changes constraint value to format expected
|
||||
// by Parse.
|
||||
if (
|
||||
fields[parentFieldName] &&
|
||||
fields[parentFieldName].type === 'Pointer' &&
|
||||
typeof fieldValue === 'string'
|
||||
) {
|
||||
const { targetClass } = fields[parentFieldName];
|
||||
let objectId = fieldValue;
|
||||
const globalIdObject = fromGlobalId(objectId);
|
||||
if (globalIdObject.type === targetClass) {
|
||||
objectId = globalIdObject.id;
|
||||
} else if (
|
||||
fields[parentFieldName] &&
|
||||
(fields[parentFieldName].type === 'Pointer' ||
|
||||
fields[parentFieldName].type === 'Relation')
|
||||
) {
|
||||
const { targetClass } = fields[parentFieldName];
|
||||
if (fieldName === 'exists') {
|
||||
if (fields[parentFieldName].type === 'Relation') {
|
||||
const whereTarget = fieldValue ? 'where' : 'notWhere';
|
||||
if (constraints[whereTarget]) {
|
||||
if (constraints[whereTarget].objectId) {
|
||||
constraints[whereTarget].objectId = {
|
||||
...constraints[whereTarget].objectId,
|
||||
$exists: fieldValue,
|
||||
};
|
||||
} else {
|
||||
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] = {
|
||||
__type: 'Pointer',
|
||||
className: targetClass,
|
||||
objectId,
|
||||
};
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
case '$point':
|
||||
@@ -170,20 +235,21 @@ const transformQueryConstraintInputToParse = (
|
||||
}
|
||||
if (typeof fieldValue === 'object') {
|
||||
if (fieldName === 'where') {
|
||||
transformQueryInputToParse(fieldValue);
|
||||
transformQueryInputToParse(fieldValue, className, parseClasses);
|
||||
} else {
|
||||
transformQueryConstraintInputToParse(
|
||||
fieldValue,
|
||||
fields,
|
||||
fieldName,
|
||||
constraints
|
||||
className,
|
||||
constraints,
|
||||
parseClasses
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const transformQueryInputToParse = (constraints, fields, className) => {
|
||||
const transformQueryInputToParse = (constraints, className, parseClasses) => {
|
||||
if (!constraints || typeof constraints !== 'object') {
|
||||
return;
|
||||
}
|
||||
@@ -195,42 +261,17 @@ const transformQueryInputToParse = (constraints, fields, className) => {
|
||||
delete constraints[fieldName];
|
||||
fieldName = parseQueryMap[fieldName];
|
||||
constraints[fieldName] = fieldValue;
|
||||
|
||||
if (fieldName !== 'objectId') {
|
||||
fieldValue.forEach(fieldValueItem => {
|
||||
transformQueryInputToParse(fieldValueItem, fields, className);
|
||||
});
|
||||
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') {
|
||||
fieldValue.forEach(fieldValueItem => {
|
||||
transformQueryInputToParse(fieldValueItem, className, parseClasses);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
transformQueryConstraintInputToParse(
|
||||
fieldValue,
|
||||
fields,
|
||||
fieldName,
|
||||
constraints
|
||||
className,
|
||||
constraints,
|
||||
parseClasses
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user