GraphQL Object constraints (#5715)
* 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
This commit is contained in:
committed by
Antonio Davi Macedo Coelho de Castro
parent
e0690d0c56
commit
ef14ca530d
@@ -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();
|
||||
|
||||
@@ -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`
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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"]);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const { ParseWebSocketServer } = require('../lib/LiveQuery/ParseWebSocketServer');
|
||||
const {
|
||||
ParseWebSocketServer,
|
||||
} = require('../lib/LiveQuery/ParseWebSocketServer');
|
||||
|
||||
describe('ParseWebSocketServer', function() {
|
||||
beforeEach(function(done) {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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 => {
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user