Skip to content

Commit

Permalink
Merge pull request #196 from darrellwarde/bugfix/#190-cypher-all-any
Browse files Browse the repository at this point in the history
Replace ALL with ANY in whilst querying relationship equality
  • Loading branch information
darrellwarde authored May 10, 2021
2 parents 912feef + b20d016 commit 935f954
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 3 deletions.
4 changes: 2 additions & 2 deletions packages/graphql/src/translate/create-where-and-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ function createWhereAndParams({

let resultStr = [
`EXISTS((${varName})${inStr}${relTypeStr}${outStr}(:${equalityRelation.typeMeta.name}))`,
`AND ALL(${param} IN [(${varName})${inStr}${relTypeStr}${outStr}(${param}:${equalityRelation.typeMeta.name}) | ${param}] INNER_WHERE `,
`AND ANY(${param} IN [(${varName})${inStr}${relTypeStr}${outStr}(${param}:${equalityRelation.typeMeta.name}) | ${param}] INNER_WHERE `,
].join(" ");

const recurse = createWhereAndParams({
Expand All @@ -299,7 +299,7 @@ function createWhereAndParams({
});

resultStr += recurse[0];
resultStr += ")"; // close ALL
resultStr += ")"; // close ANY
res.clauses.push(resultStr);
res.params = { ...res.params, ...recurse[1] };
return res;
Expand Down
213 changes: 213 additions & 0 deletions packages/graphql/tests/integration/issues/#190.int.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Driver } from "neo4j-driver";
import { graphql } from "graphql";
import { gql } from "apollo-server";
import neo4j from "../neo4j";
import { Neo4jGraphQL } from "../../../src/classes";

describe("https://github.com/neo4j/graphql/issues/190", () => {
let driver: Driver;
const typeDefs = gql`
type User {
client_id: String
uid: String
demographics: [UserDemographics] @relationship(type: "HAS_DEMOGRAPHIC", direction: OUT)
}
type UserDemographics {
client_id: String
type: String
value: String
users: [User] @relationship(type: "HAS_DEMOGRAPHIC", direction: IN)
}
`;

beforeAll(async () => {
driver = await neo4j();
const session = driver.session();

try {
await session.run(
"CREATE (:User {uid: 'user1'}),(:User {uid: 'user2'}),(:UserDemographics{type:'Gender',value:'Female'}),(:UserDemographics{type:'Gender',value:'Male'}),(:UserDemographics{type:'Age',value:'50+'}),(:UserDemographics{type:'State',value:'VIC'})"
);
await session.run(
"MATCH (u:User {uid: 'user1'}) MATCH (d:UserDemographics {type: 'Gender', value:'Female'}) CREATE (u)-[:HAS_DEMOGRAPHIC]->(d)"
);
await session.run(
"MATCH (u:User {uid: 'user2'}) MATCH (d:UserDemographics {type: 'Gender', value:'Male'}) CREATE (u)-[:HAS_DEMOGRAPHIC]->(d)"
);
await session.run(
"MATCH (u:User {uid: 'user1'}) MATCH (d:UserDemographics {type: 'Age', value:'50+'}) CREATE (u)-[:HAS_DEMOGRAPHIC]->(d)"
);
await session.run(
"MATCH (u:User {uid: 'user2'}) MATCH (d:UserDemographics {type: 'Age', value:'50+'}) CREATE (u)-[:HAS_DEMOGRAPHIC]->(d)"
);
await session.run(
"MATCH (u:User {uid: 'user1'}) MATCH (d:UserDemographics {type: 'State', value:'VIC'}) CREATE (u)-[:HAS_DEMOGRAPHIC]->(d)"
);
await session.run(
"MATCH (u:User {uid: 'user2'}) MATCH (d:UserDemographics {type: 'State', value:'VIC'}) CREATE (u)-[:HAS_DEMOGRAPHIC]->(d)"
);
} finally {
await session.close();
}
});

afterAll(async () => {
const session = driver.session();

try {
await session.run("MATCH (u:User) WHERE (u.uid = 'user1' OR u.uid = 'user2') DETACH DELETE u");
await session.run(
"MATCH (ud:UserDemographics) WHERE ((ud.type = 'Gender' AND ud.value = 'Female') OR (ud.type = 'Gender' AND ud.value = 'Male') OR (ud.type = 'Age' AND ud.value = '50+') OR (ud.type = 'State' AND ud.value = 'VIC')) DELETE ud"
);
} finally {
await session.close();
}

await driver.close();
});

test("Example 1", async () => {
const session = driver.session();

const neoSchema = new Neo4jGraphQL({ typeDefs, driver });

const query = `
query {
users(where: { demographics: { type: "Gender", value: "Female" } }) {
uid
demographics {
type
value
}
}
}
`;

try {
await neoSchema.checkNeo4jCompat();

const result = await graphql({
schema: neoSchema.schema,
source: query,
contextValue: { driver },
});

expect(result.errors).toBeFalsy();

expect(result?.data?.users).toEqual([
{
uid: "user1",
demographics: [
{
type: "State",
value: "VIC",
},
{
type: "Age",
value: "50+",
},
{
type: "Gender",
value: "Female",
},
],
},
]);
} finally {
await session.close();
}
});

test("Example 2", async () => {
const session = driver.session();

const neoSchema = new Neo4jGraphQL({ typeDefs, driver });

const query = `
query {
users(
where: {
demographics: { OR: [{ type: "Gender", value: "Female" }, { type: "State" }, { type: "Age" }] }
}
) {
uid
demographics {
type
value
}
}
}
`;

try {
await neoSchema.checkNeo4jCompat();

const result = await graphql({
schema: neoSchema.schema,
source: query,
contextValue: { driver },
});

expect(result.errors).toBeFalsy();

expect(result?.data?.users).toEqual([
{
uid: "user1",
demographics: [
{
type: "State",
value: "VIC",
},
{
type: "Age",
value: "50+",
},
{
type: "Gender",
value: "Female",
},
],
},
{
uid: "user2",
demographics: [
{
type: "State",
value: "VIC",
},
{
type: "Age",
value: "50+",
},
{
type: "Gender",
value: "Male",
},
],
},
]);
} finally {
await session.close();
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ RETURN this { .budget } as this

```cypher
MATCH (this:Movie)
WHERE EXISTS((this)-[:IN_GENRE]->(:Genre)) AND ALL(this_genres IN [(this)-[:IN_GENRE]->(this_genres:Genre) | this_genres] WHERE this_genres.name = $this_genres_name)
WHERE EXISTS((this)-[:IN_GENRE]->(:Genre)) AND ANY(this_genres IN [(this)-[:IN_GENRE]->(this_genres:Genre) | this_genres] WHERE this_genres.name = $this_genres_name)
RETURN this { .actorCount } as this
```

Expand Down
104 changes: 104 additions & 0 deletions packages/graphql/tests/tck/tck-test-files/cypher/issues/#190.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
## #190

<https://github.com/neo4j/graphql/issues/190>

Type definitions:

```schema
type User {
client_id: String
uid: String
demographics: [UserDemographics] @relationship(type: "HAS_DEMOGRAPHIC", direction: OUT)
}
type UserDemographics {
client_id: String
type: String
value: String
users: [User] @relationship(type: "HAS_DEMOGRAPHIC", direction: IN)
}
```

---

### Example 1

**GraphQL input**

```graphql
query {
users(where: { demographics: { type: "Gender", value: "Female" } }) {
uid
demographics {
type
value
}
}
}
```

**Expected Cypher output**

```cypher
MATCH (this:User)
WHERE EXISTS((this)-[:HAS_DEMOGRAPHIC]->(:UserDemographics)) AND ANY(this_demographics IN [(this)-[:HAS_DEMOGRAPHIC]->(this_demographics:UserDemographics) | this_demographics] WHERE this_demographics.type = $this_demographics_type AND this_demographics.value = $this_demographics_value)
RETURN this { .uid, demographics: [ (this)-[:HAS_DEMOGRAPHIC]->(this_demographics:UserDemographics) | this_demographics { .type, .value } ] } as this
```

**Expected Cypher params**

```cypher-params
{
"this_demographics_type": "Gender",
"this_demographics_value": "Female"
}
```

---

### Example 2

**GraphQL input**

```graphql
query {
users(
where: {
demographics: {
OR: [
{ type: "Gender", value: "Female" }
{ type: "State" }
{ type: "Age" }
]
}
}
) {
uid
demographics {
type
value
}
}
}
```

**Expected Cypher output**

```cypher
MATCH (this:User)
WHERE EXISTS((this)-[:HAS_DEMOGRAPHIC]->(:UserDemographics)) AND ANY(this_demographics IN [(this)-[:HAS_DEMOGRAPHIC]->(this_demographics:UserDemographics) | this_demographics] WHERE (this_demographics.type = $this_demographics_OR_type AND this_demographics.value = $this_demographics_OR_value OR this_demographics.type = $this_demographics_OR1_type OR this_demographics.type = $this_demographics_OR2_type))
RETURN this { .uid, demographics: [ (this)-[:HAS_DEMOGRAPHIC]->(this_demographics:UserDemographics) | this_demographics { .type, .value } ] } as this
```

**Expected Cypher params**

```cypher-params
{
"this_demographics_OR_type": "Gender",
"this_demographics_OR_value": "Female",
"this_demographics_OR1_type": "State",
"this_demographics_OR2_type": "Age"
}
```

---

0 comments on commit 935f954

Please sign in to comment.