From 7f5a15d5df0dfa3515e9f73709d6a49663545f9b Mon Sep 17 00:00:00 2001 From: Jong Eun Lee Date: Sun, 3 Jul 2022 18:13:10 +0800 Subject: [PATCH] fix: graphQL query ignores condition `equalTo` with value `false` (#8032) --- spec/ParseGraphQLServer.spec.js | 124 ++++++++++++++++++ .../Postgres/PostgresStorageAdapter.js | 44 ++++--- src/GraphQL/transformers/query.js | 2 +- 3 files changed, 154 insertions(+), 16 deletions(-) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index adf004932b..205de6263c 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -9516,6 +9516,130 @@ describe('ParseGraphQLServer', () => { } }); + it('should support where argument on object field that contains false boolean value or 0 number value', async () => { + try { + const someObjectFieldValue1 = { + foo: { bar: true, baz: 100 }, + }; + + const someObjectFieldValue2 = { + foo: { bar: false, baz: 0 }, + }; + + const object1 = new Parse.Object('SomeClass'); + await object1.save({ + someObjectField: someObjectFieldValue1, + }); + const object2 = new Parse.Object('SomeClass'); + await object2.save({ + someObjectField: someObjectFieldValue2, + }); + + const whereToObject1 = { + someObjectField: { + equalTo: { key: 'foo.bar', value: true }, + notEqualTo: { key: 'foo.baz', value: 0 }, + }, + }; + const whereToObject2 = { + someObjectField: { + notEqualTo: { key: 'foo.bar', value: true }, + equalTo: { key: 'foo.baz', value: 0 }, + }, + }; + + const whereToAll = { + someObjectField: { + lessThan: { key: 'foo.baz', value: 101 }, + }, + }; + + const whereToNone = { + someObjectField: { + notEqualTo: { key: 'foo.bar', value: true }, + equalTo: { key: 'foo.baz', value: 1 }, + }, + }; + + const queryResult = await apolloClient.query({ + query: gql` + query GetSomeObject( + $id1: ID! + $id2: ID! + $whereToObject1: SomeClassWhereInput + $whereToObject2: SomeClassWhereInput + $whereToAll: SomeClassWhereInput + $whereToNone: SomeClassWhereInput + ) { + obj1: someClass(id: $id1) { + id + someObjectField + } + obj2: someClass(id: $id2) { + id + someObjectField + } + onlyObj1: someClasses(where: $whereToObject1) { + edges { + node { + id + someObjectField + } + } + } + onlyObj2: someClasses(where: $whereToObject2) { + edges { + node { + id + someObjectField + } + } + } + all: someClasses(where: $whereToAll) { + edges { + node { + id + someObjectField + } + } + } + none: someClasses(where: $whereToNone) { + edges { + node { + id + someObjectField + } + } + } + } + `, + variables: { + id1: object1.id, + id2: object2.id, + whereToObject1, + whereToObject2, + whereToAll, + whereToNone, + }, + }); + + const { obj1, obj2, onlyObj1, onlyObj2, all, none } = queryResult.data; + + expect(obj1.someObjectField).toEqual(someObjectFieldValue1); + expect(obj2.someObjectField).toEqual(someObjectFieldValue2); + + // Checks class query results + expect(onlyObj1.edges.length).toEqual(1); + expect(onlyObj1.edges[0].node.someObjectField).toEqual(someObjectFieldValue1); + expect(onlyObj2.edges.length).toEqual(1); + expect(onlyObj2.edges[0].node.someObjectField).toEqual(someObjectFieldValue2); + expect(all.edges.length).toEqual(2); + expect(none.edges.length).toEqual(0); + } catch (e) { + handleError(e); + } + }); + it('should support object composed queries', async () => { try { const someObjectFieldValue1 = { diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 764cfe2d78..eb668868c6 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -91,6 +91,22 @@ const toPostgresValue = value => { return value; }; +const toPostgresValueCastType = value => { + const postgresValue = toPostgresValue(value); + let castType; + switch (typeof postgresValue) { + case 'number': + castType = 'double precision'; + break; + case 'boolean': + castType = 'boolean'; + break; + default: + castType = undefined; + } + return castType; +}; + const transformValue = value => { if (typeof value === 'object' && value.__type === 'Pointer') { return value.objectId; @@ -369,9 +385,12 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus ); } else { if (fieldName.indexOf('.') >= 0) { - const constraintFieldName = transformDotField(fieldName); + const castType = toPostgresValueCastType(fieldValue.$ne); + const constraintFieldName = castType + ? `CAST ((${transformDotField(fieldName)}) AS ${castType})` + : transformDotField(fieldName); patterns.push( - `(${constraintFieldName} <> $${index} OR ${constraintFieldName} IS NULL)` + `(${constraintFieldName} <> $${index + 1} OR ${constraintFieldName} IS NULL)` ); } else if (typeof fieldValue.$ne === 'object' && fieldValue.$ne.$relativeTime) { throw new Parse.Error( @@ -401,8 +420,12 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus index += 1; } else { if (fieldName.indexOf('.') >= 0) { + const castType = toPostgresValueCastType(fieldValue.$eq); + const constraintFieldName = castType + ? `CAST ((${transformDotField(fieldName)}) AS ${castType})` + : transformDotField(fieldName); values.push(fieldValue.$eq); - patterns.push(`${transformDotField(fieldName)} = $${index++}`); + patterns.push(`${constraintFieldName} = $${index++}`); } else if (typeof fieldValue.$eq === 'object' && fieldValue.$eq.$relativeTime) { throw new Parse.Error( Parse.Error.INVALID_JSON, @@ -771,20 +794,11 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus Object.keys(ParseToPosgresComparator).forEach(cmp => { if (fieldValue[cmp] || fieldValue[cmp] === 0) { const pgComparator = ParseToPosgresComparator[cmp]; - let postgresValue = toPostgresValue(fieldValue[cmp]); let constraintFieldName; + let postgresValue = toPostgresValue(fieldValue[cmp]); + if (fieldName.indexOf('.') >= 0) { - let castType; - switch (typeof postgresValue) { - case 'number': - castType = 'double precision'; - break; - case 'boolean': - castType = 'boolean'; - break; - default: - castType = undefined; - } + const castType = toPostgresValueCastType(fieldValue[cmp]); constraintFieldName = castType ? `CAST ((${transformDotField(fieldName)}) AS ${castType})` : transformDotField(fieldName); diff --git a/src/GraphQL/transformers/query.js b/src/GraphQL/transformers/query.js index a91ee208ad..bf4946f125 100644 --- a/src/GraphQL/transformers/query.js +++ b/src/GraphQL/transformers/query.js @@ -108,7 +108,7 @@ const transformQueryConstraintInputToParse = ( * } * } */ - if (fieldValue.key && fieldValue.value && parentConstraints && parentFieldName) { + if (fieldValue.key && fieldValue.value !== undefined && parentConstraints && parentFieldName) { delete parentConstraints[parentFieldName]; parentConstraints[`${parentFieldName}.${fieldValue.key}`] = { ...parentConstraints[`${parentFieldName}.${fieldValue.key}`],