From 5d76b2f354e568bb0c84f3d6b5ae3fd56cbd83c1 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Thu, 5 Dec 2019 19:14:16 +0100 Subject: [PATCH] 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 --- spec/ParseGraphQLServer.spec.js | 326 ++++++++++++++++++++- src/GraphQL/helpers/objectsQueries.js | 5 +- src/GraphQL/loaders/defaultGraphQLTypes.js | 106 +++---- src/GraphQL/loaders/parseClassQueries.js | 2 +- src/GraphQL/loaders/parseClassTypes.js | 61 ++-- src/GraphQL/transformers/constraintType.js | 12 +- src/GraphQL/transformers/query.js | 167 +++++++---- 7 files changed, 519 insertions(+), 160 deletions(-) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index f233f393..7ba34a82 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -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({ diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js index d374db58..6b7a184d 100644 --- a/src/GraphQL/helpers/objectsQueries.js +++ b/src/GraphQL/helpers/objectsQueries.js @@ -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, diff --git a/src/GraphQL/loaders/defaultGraphQLTypes.js b/src/GraphQL/loaders/defaultGraphQLTypes.js index 7a36953e..d9ddd67d 100644 --- a/src/GraphQL/loaders/defaultGraphQLTypes.js +++ b/src/GraphQL/loaders/defaultGraphQLTypes.js @@ -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, diff --git a/src/GraphQL/loaders/parseClassQueries.js b/src/GraphQL/loaders/parseClassQueries.js index e2edf301..80667836 100644 --- a/src/GraphQL/loaders/parseClassQueries.js +++ b/src/GraphQL/loaders/parseClassQueries.js @@ -145,7 +145,7 @@ const load = function( auth, info, selectedFields, - parseClass.fields + parseGraphQLSchema.parseClasses ); } catch (e) { parseGraphQLSchema.handleError(e); diff --git a/src/GraphQL/loaders/parseClassTypes.js b/src/GraphQL/loaders/parseClassTypes.js index 73a3c2da..b0107428 100644 --- a/src/GraphQL/loaders/parseClassTypes.js +++ b/src/GraphQL/loaders/parseClassTypes.js @@ -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, diff --git a/src/GraphQL/transformers/constraintType.js b/src/GraphQL/transformers/constraintType.js index 16f0b5d6..61a6413a 100644 --- a/src/GraphQL/transformers/constraintType.js +++ b/src/GraphQL/transformers/constraintType.js @@ -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; } diff --git a/src/GraphQL/transformers/query.js b/src/GraphQL/transformers/query.js index a417ef4c..4dc71579 100644 --- a/src/GraphQL/transformers/query.js +++ b/src/GraphQL/transformers/query.js @@ -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 ); } });