Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove single relationships #5819

Merged
merged 16 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .changeset/little-lemons-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
"@neo4j/graphql": major
---

Single element relationships have been removed in favor of list relationships:

Before

```graphql
type Movie {
director: Person @relationship(type: "DIRECTED", direction: "IN")
}
```

After

```graphql
type Movie {
director: [Person!]! @relationship(type: "DIRECTED", direction: "IN")
}
```

This requires updating filters, clients and auth rules to use the list filter operations.

Single element relationships cannot be reliably enforced, leading to a data inconsistent with the schema. If the GraphQL model requires 1-1 relationships (such as in federations) these can now be achieved with the `@cypher` directive instead:

```graphql
type Movie {
director: Person
@cypher(
statement: """
MATCH(this)-[:ACTED_IN]->(p:Person)
RETURN p
"""
columnName: "p"
)
}
```
60 changes: 48 additions & 12 deletions packages/apollo-federation-subgraph-compatibility/src/type-defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,39 +42,75 @@ export const typeDefs = gql`

directive @custom on OBJECT

type Product @custom @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") {
type Product @custom @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") @node {
id: ID!
sku: String
package: String
variation: ProductVariation @relationship(type: "HAS_VARIATION", direction: OUT)
dimensions: ProductDimension @relationship(type: "HAS_DIMENSIONS", direction: OUT)
createdBy: User @provides(fields: "totalProductsCreated") @relationship(type: "CREATED_BY", direction: OUT)
variation: ProductVariation
@cypher(
statement: """
MATCH (this)-[:HAS_VARIATION]->(res:ProductVariation)
RETURN res
"""
columnName: "res"
)
dimensions: ProductDimension
@cypher(
statement: """
MATCH (this)-[:HAS_DIMENSIONS]->(res:ProductDimension)
RETURN res
"""
columnName: "res"
)
createdBy: User
@provides(fields: "totalProductsCreated")
@cypher(
statement: """
MATCH (this)-[:CREATED_BY]->(res:User)
RETURN res
"""
columnName: "res"
)
notes: String @tag(name: "internal")
research: [ProductResearch!]! @relationship(type: "HAS_RESEARCH", direction: OUT)
}

type DeprecatedProduct @key(fields: "sku package") {
type DeprecatedProduct @key(fields: "sku package") @node {
sku: String!
package: String!
reason: String
createdBy: User @relationship(type: "CREATED_BY", direction: OUT)
createdBy: User
@cypher(
statement: """
MATCH (this)-[:CREATED_BY]->(res:User)
RETURN res
"""
columnName: "res"
)
}

type ProductVariation {
type ProductVariation @node {
id: ID!
}

type ProductResearch @key(fields: "study { caseNumber }") {
study: CaseStudy! @relationship(type: "HAS_STUDY", direction: OUT)
type ProductResearch @key(fields: "study { caseNumber }") @node {
study: CaseStudy!
@cypher(
statement: """
MATCH (this)-[:HAS_STUDY]->(res:CaseStudy)
RETURN res
"""
columnName: "res"
)
outcome: String
}

type CaseStudy {
type CaseStudy @node {
caseNumber: ID!
description: String
}

type ProductDimension @shareable {
type ProductDimension @shareable @node {
size: String
weight: Float
unit: String @inaccessible
Expand All @@ -93,7 +129,7 @@ export const typeDefs = gql`

# Should be extend type as below
# extend type User @key(fields: "email") {
type User @key(fields: "email") @extends {
type User @key(fields: "email") @node @extends {
averageProductsCreatedPerYear: Int @requires(fields: "totalProductsCreated yearsOfEmployment")
email: ID! @external
name: String @override(from: "users")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export function createRelationshipFields({
userDefinedFieldDirectivesForNode: Map<string, Map<string, DirectiveNode[]>>;
features?: Neo4jFeaturesSettings;
}): void {
// HERE?
const relationships =
entityAdapter instanceof ConcreteEntityAdapter
? entityAdapter.relationships
Expand All @@ -190,6 +191,12 @@ export function createRelationshipFields({
return;
}

if (!relationshipAdapter.isList) {
throw new Error(
`@relationship on non-list field [${relationshipAdapter.source.name}.${relationshipAdapter.name}] not supported`
);
}

// TODO: find a way to merge these 2 into 1 RelationshipProperties generation function
if (relationshipAdapter instanceof RelationshipDeclarationAdapter) {
doForRelationshipDeclaration({
Expand Down

This file was deleted.

Loading
Loading