diff --git a/.changeset/cool-wasps-rest.md b/.changeset/cool-wasps-rest.md new file mode 100644 index 0000000000..a76042aa72 --- /dev/null +++ b/.changeset/cool-wasps-rest.md @@ -0,0 +1,5 @@ +--- +"@neo4j/graphql": patch +--- + +Generate filters for non-list relationship fields if deprecated array filters have been excluded diff --git a/packages/graphql/src/schema/generation/augment-where-input.ts b/packages/graphql/src/schema/generation/augment-where-input.ts index 60cf9fb675..fcb2b08a3f 100644 --- a/packages/graphql/src/schema/generation/augment-where-input.ts +++ b/packages/graphql/src/schema/generation/augment-where-input.ts @@ -49,7 +49,7 @@ function augmentWhereInputType({ return fields; } - if (shouldAddDeprecatedFields(features, "arrayFilters")) { + if (!relationshipAdapter.isList || shouldAddDeprecatedFields(features, "arrayFilters")) { fields[fieldName] = { type: whereType, }; diff --git a/packages/graphql/tests/schema/issues/5435.test.ts b/packages/graphql/tests/schema/issues/5435.test.ts new file mode 100644 index 0000000000..c2cdd5fc99 --- /dev/null +++ b/packages/graphql/tests/schema/issues/5435.test.ts @@ -0,0 +1,411 @@ +/* + * 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 { printSchemaWithDirectives } from "@graphql-tools/utils"; +import { gql } from "graphql-tag"; +import { lexicographicSortSchema } from "graphql/utilities"; +import { Neo4jGraphQL } from "../../../src"; + +describe("https://github.com/neo4j/graphql/issues/5435", () => { + test("filters should be generated for non-list relationships with array filters disabled", async () => { + const typeDefs = gql` + type User { + id: ID! @id + } + + type Post @authorization(validate: [{ where: { node: { author: { id: "$jwt.sub" } } } }]) { + title: String! + content: String! + author: User! @relationship(type: "AUTHORED", direction: IN) + } + `; + const neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + excludeDeprecatedFields: { + arrayFilters: true, + }, + authorization: { + key: "key", + }, + }, + }); + const printedSchema = printSchemaWithDirectives(lexicographicSortSchema(await neoSchema.getSchema())); + + expect(printedSchema).toMatchInlineSnapshot(` + "schema { + query: Query + mutation: Mutation + } + + \\"\\"\\" + Information about the number of nodes and relationships created during a create mutation + \\"\\"\\" + type CreateInfo { + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") + nodesCreated: Int! + relationshipsCreated: Int! + } + + type CreatePostsMutationResponse { + info: CreateInfo! + posts: [Post!]! + } + + type CreateUsersMutationResponse { + info: CreateInfo! + users: [User!]! + } + + \\"\\"\\" + Information about the number of nodes and relationships deleted during a delete mutation + \\"\\"\\" + type DeleteInfo { + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") + nodesDeleted: Int! + relationshipsDeleted: Int! + } + + type IDAggregateSelection { + longest: ID + shortest: ID + } + + type Mutation { + createPosts(input: [PostCreateInput!]!): CreatePostsMutationResponse! + createUsers(input: [UserCreateInput!]!): CreateUsersMutationResponse! + deletePosts(delete: PostDeleteInput, where: PostWhere): DeleteInfo! + deleteUsers(where: UserWhere): DeleteInfo! + updatePosts(connect: PostConnectInput, create: PostRelationInput, delete: PostDeleteInput, disconnect: PostDisconnectInput, update: PostUpdateInput, where: PostWhere): UpdatePostsMutationResponse! + updateUsers(update: UserUpdateInput, where: UserWhere): UpdateUsersMutationResponse! + } + + \\"\\"\\"Pagination information (Relay)\\"\\"\\" + type PageInfo { + endCursor: String + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: String + } + + type Post { + author(directed: Boolean = true, options: UserOptions, where: UserWhere): User! + authorAggregate(directed: Boolean = true, where: UserWhere): PostUserAuthorAggregationSelection + authorConnection(after: String, directed: Boolean = true, first: Int, sort: [PostAuthorConnectionSort!], where: PostAuthorConnectionWhere): PostAuthorConnection! + content: String! + title: String! + } + + type PostAggregateSelection { + content: StringAggregateSelection! + count: Int! + title: StringAggregateSelection! + } + + input PostAuthorAggregateInput { + AND: [PostAuthorAggregateInput!] + NOT: PostAuthorAggregateInput + OR: [PostAuthorAggregateInput!] + count: Int + count_GT: Int + count_GTE: Int + count_LT: Int + count_LTE: Int + node: PostAuthorNodeAggregationWhereInput + } + + input PostAuthorConnectFieldInput { + \\"\\"\\" + Whether or not to overwrite any matching relationship with the new properties. + \\"\\"\\" + overwrite: Boolean! = true + where: UserConnectWhere + } + + type PostAuthorConnection { + edges: [PostAuthorRelationship!]! + pageInfo: PageInfo! + totalCount: Int! + } + + input PostAuthorConnectionSort { + node: UserSort + } + + input PostAuthorConnectionWhere { + AND: [PostAuthorConnectionWhere!] + NOT: PostAuthorConnectionWhere + OR: [PostAuthorConnectionWhere!] + node: UserWhere + node_NOT: UserWhere @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + } + + input PostAuthorCreateFieldInput { + node: UserCreateInput! + } + + input PostAuthorDeleteFieldInput { + where: PostAuthorConnectionWhere + } + + input PostAuthorDisconnectFieldInput { + where: PostAuthorConnectionWhere + } + + input PostAuthorFieldInput { + connect: PostAuthorConnectFieldInput + create: PostAuthorCreateFieldInput + } + + input PostAuthorNodeAggregationWhereInput { + AND: [PostAuthorNodeAggregationWhereInput!] + NOT: PostAuthorNodeAggregationWhereInput + OR: [PostAuthorNodeAggregationWhereInput!] + id_EQUAL: ID @deprecated(reason: \\"Aggregation filters that are not relying on an aggregating function will be deprecated.\\") + } + + type PostAuthorRelationship { + cursor: String! + node: User! + } + + input PostAuthorUpdateConnectionInput { + node: UserUpdateInput + } + + input PostAuthorUpdateFieldInput { + connect: PostAuthorConnectFieldInput + create: PostAuthorCreateFieldInput + delete: PostAuthorDeleteFieldInput + disconnect: PostAuthorDisconnectFieldInput + update: PostAuthorUpdateConnectionInput + where: PostAuthorConnectionWhere + } + + input PostConnectInput { + author: PostAuthorConnectFieldInput + } + + input PostCreateInput { + author: PostAuthorFieldInput + content: String! + title: String! + } + + input PostDeleteInput { + author: PostAuthorDeleteFieldInput + } + + input PostDisconnectInput { + author: PostAuthorDisconnectFieldInput + } + + type PostEdge { + cursor: String! + node: Post! + } + + input PostOptions { + limit: Int + offset: Int + \\"\\"\\" + Specify one or more PostSort objects to sort Posts by. The sorts will be applied in the order in which they are arranged in the array. + \\"\\"\\" + sort: [PostSort!] + } + + input PostRelationInput { + author: PostAuthorCreateFieldInput + } + + \\"\\"\\" + Fields to sort Posts by. The order in which sorts are applied is not guaranteed when specifying many fields in one PostSort object. + \\"\\"\\" + input PostSort { + content: SortDirection + title: SortDirection + } + + input PostUpdateInput { + author: PostAuthorUpdateFieldInput + content: String + title: String + } + + type PostUserAuthorAggregationSelection { + count: Int! + node: PostUserAuthorNodeAggregateSelection + } + + type PostUserAuthorNodeAggregateSelection { + id: IDAggregateSelection! + } + + input PostWhere { + AND: [PostWhere!] + NOT: PostWhere + OR: [PostWhere!] + author: UserWhere + authorAggregate: PostAuthorAggregateInput + authorConnection: PostAuthorConnectionWhere + authorConnection_NOT: PostAuthorConnectionWhere + author_NOT: UserWhere + content: String + content_CONTAINS: String + content_ENDS_WITH: String + content_IN: [String!] + content_NOT: String @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + content_NOT_CONTAINS: String @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + content_NOT_ENDS_WITH: String @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + content_NOT_IN: [String!] @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + content_NOT_STARTS_WITH: String @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + content_STARTS_WITH: String + title: String + title_CONTAINS: String + title_ENDS_WITH: String + title_IN: [String!] + title_NOT: String @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + title_NOT_CONTAINS: String @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + title_NOT_ENDS_WITH: String @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + title_NOT_IN: [String!] @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + title_NOT_STARTS_WITH: String @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + title_STARTS_WITH: String + } + + type PostsConnection { + edges: [PostEdge!]! + pageInfo: PageInfo! + totalCount: Int! + } + + type Query { + posts(options: PostOptions, where: PostWhere): [Post!]! + postsAggregate(where: PostWhere): PostAggregateSelection! + postsConnection(after: String, first: Int, sort: [PostSort], where: PostWhere): PostsConnection! + users(options: UserOptions, where: UserWhere): [User!]! + usersAggregate(where: UserWhere): UserAggregateSelection! + usersConnection(after: String, first: Int, sort: [UserSort], where: UserWhere): UsersConnection! + } + + \\"\\"\\"An enum for sorting in either ascending or descending order.\\"\\"\\" + enum SortDirection { + \\"\\"\\"Sort by field values in ascending order.\\"\\"\\" + ASC + \\"\\"\\"Sort by field values in descending order.\\"\\"\\" + DESC + } + + type StringAggregateSelection { + longest: String + shortest: String + } + + \\"\\"\\" + Information about the number of nodes and relationships created and deleted during an update mutation + \\"\\"\\" + type UpdateInfo { + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") + nodesCreated: Int! + nodesDeleted: Int! + relationshipsCreated: Int! + relationshipsDeleted: Int! + } + + type UpdatePostsMutationResponse { + info: UpdateInfo! + posts: [Post!]! + } + + type UpdateUsersMutationResponse { + info: UpdateInfo! + users: [User!]! + } + + type User { + id: ID! + } + + type UserAggregateSelection { + count: Int! + id: IDAggregateSelection! + } + + input UserConnectWhere { + node: UserWhere! + } + + input UserCreateInput { + \\"\\"\\" + Appears because this input type would be empty otherwise because this type is composed of just generated and/or relationship properties. See https://neo4j.com/docs/graphql-manual/current/troubleshooting/faqs/ + \\"\\"\\" + _emptyInput: Boolean + } + + type UserEdge { + cursor: String! + node: User! + } + + input UserOptions { + limit: Int + offset: Int + \\"\\"\\" + Specify one or more UserSort objects to sort Users by. The sorts will be applied in the order in which they are arranged in the array. + \\"\\"\\" + sort: [UserSort!] + } + + \\"\\"\\" + Fields to sort Users by. The order in which sorts are applied is not guaranteed when specifying many fields in one UserSort object. + \\"\\"\\" + input UserSort { + id: SortDirection + } + + input UserUpdateInput { + \\"\\"\\" + Appears because this input type would be empty otherwise because this type is composed of just generated and/or relationship properties. See https://neo4j.com/docs/graphql-manual/current/troubleshooting/faqs/ + \\"\\"\\" + _emptyInput: Boolean + } + + input UserWhere { + AND: [UserWhere!] + NOT: UserWhere + OR: [UserWhere!] + id: ID + id_CONTAINS: ID + id_ENDS_WITH: ID + id_IN: [ID!] + id_NOT: ID @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + id_NOT_CONTAINS: ID @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + id_NOT_ENDS_WITH: ID @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + id_NOT_IN: [ID!] @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + id_NOT_STARTS_WITH: ID @deprecated(reason: \\"Negation filters will be deprecated, use the NOT operator to achieve the same behavior\\") + id_STARTS_WITH: ID + } + + type UsersConnection { + edges: [UserEdge!]! + pageInfo: PageInfo! + totalCount: Int! + }" + `); + }); +});