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: [
|
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({
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user