Skip to content

Commit

Permalink
GraphQL Object constraints (#5715)
Browse files Browse the repository at this point in the history
* GraphQL Object constraints

Implements the GraphQL Object constraints, which allows us to filter queries results using the `$eq`, `$lt`, `$gt`, `$in`, and other Parse supported constraints.
Example:
```
query objects {
  findMyClass(where: {
    objField: {
      _eq: {
        key: 'foo.bar',
        value: 'hello'
      },
      _gt: {
        key: 'foo.number',
        value: 10
      },
      _lt: {
        key: 'anotherNumber',
        value: 5
      }
    }
  }) {
    results {
      objectId
    }
  }
}
```
In the example above, we have the `findMyClass` query (automatically generated for the `MyClass` class), and a field named `objField` whose type is Object. The object below represents a valid `objField` value and would satisfy all constraints:
```
{
  "foo": {
    "bar": "hello",
    "number": 11
  },
  "anotherNumber": 4
}
```
The Object constraint is applied only when using Parse class object type queries. When using "generic" queries such as `get` and `find`, this type of constraint is not available.

* Objects constraints not working on Postgres

Fixes the $eq, $ne, $gt, and $lt constraints when applied on an Object type field.

* Fix object constraint field name

* Fix Postgres constraints indexes

* fix: Object type composed constraints not working

* fix: Rename key and value fields

* refactor: Object constraints for generic queries

* fix: Object constraints not working on Postgres
  • Loading branch information
douglasmuraoka authored and davimacedo committed Aug 2, 2019
1 parent e0690d0 commit ef14ca5
Show file tree
Hide file tree
Showing 14 changed files with 800 additions and 522 deletions.
4 changes: 3 additions & 1 deletion spec/AuthenticationAdapters.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1178,7 +1178,9 @@ describe('phant auth adapter', () => {
};
const { adapter } = authenticationLoader.loadAuthAdapter('phantauth', {});

spyOn(httpsRequest, 'get').and.callFake(() => Promise.resolve({ sub: 'invalidID' }));
spyOn(httpsRequest, 'get').and.callFake(() =>
Promise.resolve({ sub: 'invalidID' })
);
try {
await adapter.validateAuthData(authData);
fail();
Expand Down
168 changes: 160 additions & 8 deletions spec/ParseGraphQLServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5273,7 +5273,10 @@ describe('ParseGraphQLServer', () => {
});

it('should support object values', async () => {
const someFieldValue = { foo: 'bar' };
const someFieldValue = {
foo: { bar: 'baz' },
number: 10,
};

const createResult = await apolloClient.mutate({
mutation: gql`
Expand Down Expand Up @@ -5314,30 +5317,179 @@ describe('ParseGraphQLServer', () => {
},
});

const getResult = await apolloClient.query({
const where = {
someField: {
_eq: { _key: 'foo.bar', _value: 'baz' },
_ne: { _key: 'foo.bar', _value: 'bat' },
_gt: { _key: 'number', _value: 9 },
_lt: { _key: 'number', _value: 11 },
},
};
const queryResult = await apolloClient.query({
query: gql`
query GetSomeObject($objectId: ID!) {
query GetSomeObject(
$objectId: ID!
$where: SomeClassConstraints
$genericWhere: Object
) {
objects {
get(className: "SomeClass", objectId: $objectId)
findSomeClass(where: { someField: { _exists: true } }) {
findSomeClass(where: $where) {
results {
objectId
someField
}
}
find(className: "SomeClass", where: $genericWhere) {
results
}
}
}
`,
variables: {
objectId: createResult.data.objects.create.objectId,
where,
genericWhere: where, // where and genericWhere types are different
},
});

const { someField } = getResult.data.objects.get;
const {
get: getResult,
findSomeClass,
find,
} = queryResult.data.objects;

const { someField } = getResult;
expect(typeof someField).toEqual('object');
expect(someField).toEqual(someFieldValue);
expect(getResult.data.objects.findSomeClass.results.length).toEqual(
2
);

// Checks class query results
expect(findSomeClass.results.length).toEqual(2);
expect(findSomeClass.results[0].someField).toEqual(someFieldValue);
expect(findSomeClass.results[1].someField).toEqual(someFieldValue);

// Checks generic query results
expect(find.results.length).toEqual(2);
expect(find.results[0].someField).toEqual(someFieldValue);
expect(find.results[1].someField).toEqual(someFieldValue);
});

it('should support object composed queries', async () => {
const someFieldValue = {
lorem: 'ipsum',
number: 10,
};
const someFieldValue2 = {
foo: {
test: 'bar',
},
number: 10,
};

const createResult = await apolloClient.mutate({
mutation: gql`
mutation CreateSomeObject($fields: Object, $fields2: Object) {
objects {
create1: create(className: "SomeClass", fields: $fields) {
objectId
}
create2: create(className: "SomeClass", fields: $fields2) {
objectId
}
}
}
`,
variables: {
fields: {
someField: someFieldValue,
},
fields2: {
someField: someFieldValue2,
},
},
});

await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();

const where = {
_and: [
{
someField: {
_gt: { _key: 'number', _value: 9 },
},
},
{
someField: {
_lt: { _key: 'number', _value: 11 },
},
},
{
_or: [
{
someField: {
_eq: { _key: 'lorem', _value: 'ipsum' },
},
},
{
someField: {
_eq: { _key: 'foo.test', _value: 'bar' },
},
},
],
},
],
};
const findResult = await apolloClient.query({
query: gql`
query FindSomeObject(
$where: SomeClassConstraints
$genericWhere: Object
) {
objects {
findSomeClass(where: $where) {
results {
objectId
someField
}
}
find(className: "SomeClass", where: $genericWhere) {
results
}
}
}
`,
variables: {
where,
genericWhere: where, // where and genericWhere types are different
},
});

const { create1, create2 } = createResult.data.objects;
const { findSomeClass, find } = findResult.data.objects;

// Checks class query results
const { results } = findSomeClass;
expect(results.length).toEqual(2);
expect(
results.find(result => result.objectId === create1.objectId)
.someField
).toEqual(someFieldValue);
expect(
results.find(result => result.objectId === create2.objectId)
.someField
).toEqual(someFieldValue2);

// Checks generic query results
const { results: genericResults } = find;
expect(genericResults.length).toEqual(2);
expect(
genericResults.find(result => result.objectId === create1.objectId)
.someField
).toEqual(someFieldValue);
expect(
genericResults.find(result => result.objectId === create2.objectId)
.someField
).toEqual(someFieldValue2);
});

it('should support array values', async () => {
Expand Down
2 changes: 1 addition & 1 deletion spec/ParseQuery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ describe('Parse.Query testing', () => {
});

it('nested containedIn string with single quote', async () => {
const obj = new TestObject({ nested: { foo: ["single'quote"]} });
const obj = new TestObject({ nested: { foo: ["single'quote"] } });
await obj.save();
const query = new Parse.Query(TestObject);
query.containedIn('nested.foo', ["single'quote"]);
Expand Down
4 changes: 3 additions & 1 deletion spec/ParseWebSocketServer.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const { ParseWebSocketServer } = require('../lib/LiveQuery/ParseWebSocketServer');
const {
ParseWebSocketServer,
} = require('../lib/LiveQuery/ParseWebSocketServer');

describe('ParseWebSocketServer', function() {
beforeEach(function(done) {
Expand Down
4 changes: 3 additions & 1 deletion spec/Schema.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,9 @@ describe('SchemaController', () => {
)
)
.then(actualSchema => {
expect(dd(actualSchema.classLevelPermissions, newLevelPermissions)).toEqual(undefined);
expect(
dd(actualSchema.classLevelPermissions, newLevelPermissions)
).toEqual(undefined);
done();
})
.catch(error => {
Expand Down
20 changes: 14 additions & 6 deletions spec/schemas.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,12 @@ describe('schemas', () => {
foo4: { type: 'Date', required: true },
foo5: { type: 'Number', defaultValue: 5 },
ptr: { type: 'Pointer', targetClass: 'SomeClass', required: false },
defaultFalse: { type: 'Boolean', required: true, defaultValue: false },
defaultZero: { type: 'Number', defaultValue: 0 }
defaultFalse: {
type: 'Boolean',
required: true,
defaultValue: false,
},
defaultZero: { type: 'Number', defaultValue: 0 },
},
},
}).then(async response => {
Expand All @@ -447,8 +451,12 @@ describe('schemas', () => {
foo4: { type: 'Date', required: true },
foo5: { type: 'Number', defaultValue: 5 },
ptr: { type: 'Pointer', targetClass: 'SomeClass', required: false },
defaultFalse: { type: 'Boolean', required: true, defaultValue: false },
defaultZero: { type: 'Number', defaultValue: 0 }
defaultFalse: {
type: 'Boolean',
required: true,
defaultValue: false,
},
defaultZero: { type: 'Number', defaultValue: 0 },
},
classLevelPermissions: defaultClassLevelPermissions,
});
Expand All @@ -468,8 +476,8 @@ describe('schemas', () => {
expect(obj.get('foo4')).toEqual(date);
expect(obj.get('foo5')).toEqual(5);
expect(obj.get('ptr')).toBeUndefined();
expect(obj.get('defaultFalse')).toEqual(false)
expect(obj.get('defaultZero')).toEqual(0)
expect(obj.get('defaultFalse')).toEqual(false);
expect(obj.get('defaultZero')).toEqual(0);
expect(obj.get('ptr')).toBeUndefined();
done();
});
Expand Down
Loading

0 comments on commit ef14ca5

Please sign in to comment.