diff --git a/modules/ROOT/pages/directives/custom-logic.adoc b/modules/ROOT/pages/directives/custom-logic.adoc index 9d8ce5dd..d860c50f 100644 --- a/modules/ROOT/pages/directives/custom-logic.adoc +++ b/modules/ROOT/pages/directives/custom-logic.adoc @@ -261,7 +261,7 @@ type Mutation { == `@coalesce` -When translating from GraphQL to Cypher, any instances of fields to which this directive is applied will be wrapped in a `coalesce()` function in the WHERE clause. +When translating from GraphQL to Cypher, any instances of fields to which this directive is applied will be wrapped in a `coalesce()` function in the WHERE and RETURN clause. For more information, see link:https://neo4j.com/developer/kb/understanding-non-existent-properties-and-null-values/#_use_coalesce_to_use_a_default_for_a_null_value[Understanding non-existent properties and working with nulls]. This directive helps querying against non-existent properties in a database. diff --git a/modules/ROOT/pages/directives/database-mapping.adoc b/modules/ROOT/pages/directives/database-mapping.adoc index 7794f7b8..5ef4d292 100644 --- a/modules/ROOT/pages/directives/database-mapping.adoc +++ b/modules/ROOT/pages/directives/database-mapping.adoc @@ -35,8 +35,7 @@ type Movie @node { [NOTE] ==== -In version 6.x, it's not required to specify every GraphQL type representing a Neo4j node with the `@node` directive. -In the future, types without the `@node` directive will no longer be treated as Neo4j nodes. +Types without the `@node` directive are not treated as Neo4j nodes, and queries and mutations are not generated for them. ==== When not differently specified, the GraphQL type name is used as a label for the represented Neo4j node. It's possible to explicitly define the Neo4j node labels by using the parameter `labels`. @@ -224,12 +223,12 @@ To add two node types, "Movie" and "Actor", and connect the two: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } -type Actor { +type Actor @node { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } @@ -264,12 +263,12 @@ For example, for the "ACTED_IN" relationship, add a property "roles": [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") } -type Actor { +type Actor @node { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") } diff --git a/modules/ROOT/pages/directives/index.adoc b/modules/ROOT/pages/directives/index.adoc index c9db97f1..5e35d66c 100644 --- a/modules/ROOT/pages/directives/index.adoc +++ b/modules/ROOT/pages/directives/index.adoc @@ -108,8 +108,6 @@ Particularly useful for types that are not correctly pluralized or are non-Engli | xref::/directives/indexes-and-constraints.adoc#_fulltext[`@fulltext`] | Indicates that there should be a fulltext index inserted into the database for the specified Node and its properties. -| xref::/directives/indexes-and-constraints.adoc#_unique[`@unique`] -| Indicates that there should be a uniqueness constraint in the database for the fields that it is applied to. | xref::/directives/indexes-and-constraints.adoc#_vector_index_search[`@vector`] | Perform a vector index search on your database either based by passing in a vector index or a search phrase. diff --git a/modules/ROOT/pages/directives/indexes-and-constraints.adoc b/modules/ROOT/pages/directives/indexes-and-constraints.adoc index 60351153..15b90209 100644 --- a/modules/ROOT/pages/directives/indexes-and-constraints.adoc +++ b/modules/ROOT/pages/directives/indexes-and-constraints.adoc @@ -16,8 +16,8 @@ For example: [source, graphql, indent=0] ---- input FullTextInput { - indexName: String - queryName: String + indexName: String! + queryName: String! fields: [String]! } diff --git a/modules/ROOT/pages/directives/schema-configuration/field-configuration.adoc b/modules/ROOT/pages/directives/schema-configuration/field-configuration.adoc index afafc388..0ec75dec 100644 --- a/modules/ROOT/pages/directives/schema-configuration/field-configuration.adoc +++ b/modules/ROOT/pages/directives/schema-configuration/field-configuration.adoc @@ -28,7 +28,6 @@ type Actor { name: String! age: Int actedIn(where: MovieWhere, sort: [MovieSort!]!, limit: Int, offset: Int, directed: Boolean = true): [Movie!]! - actedInAggregate(where: MovieWhere, directed: Boolean = true): ActorMovieActedInAggregationSelection actedInConnection(where: ActorActedInConnectionWhere, first: Int, after: String, directed: Boolean = true, sort: [ActorActedInConnectionSort!]): ActorActedInConnection! } ---- @@ -57,14 +56,13 @@ Now the type `Actor` looks like this: type Actor { name: String! actedIn(where: MovieWhere, sort: [MovieSort!]!, limit: Int, offset: Int, directed: Boolean = true): [Movie!]! - actedInAggregate(where: MovieWhere, directed: Boolean = true): ActorMovieActedInAggregationSelection actedInConnection(where: ActorActedInConnectionWhere, first: Int, after: String, directed: Boolean = true, sort: [ActorActedInConnectionSort!]): ActorActedInConnection! } ---- == `@relationship` -There are several nested operations available for every field created using the `@relationship` directive. These are `create`, `connect`, `disconnect`, `connectOrCreate`, and `delete`. +There are several nested operations available for every field created using the `@relationship` directive. These are `create`, `connect`, `disconnect`, and `delete`. However, these operations are not always needed. The `@relationship` directive allows you to define which operations should be available for a relationship by using the argument `nestedOperations`. @@ -82,7 +80,6 @@ enum NestedOperations { DELETE CONNECT DISCONNECT - CONNECT_OR_CREATE } directive @relationship( @@ -90,7 +87,7 @@ directive @relationship( queryDirection: RelationshipQueryDirection! = DEFAULT_DIRECTED direction: RelationshipDirection! properties: String - nestedOperations: [NestedOperations!]! = [CREATE, UPDATE, DELETE, CONNECT, DISCONNECT, CONNECT_OR_CREATE] + nestedOperations: [NestedOperations!]! = [CREATE, UPDATE, DELETE, CONNECT, DISCONNECT] aggregate: Boolean! = true ) on FIELD_DEFINITION ---- @@ -99,19 +96,25 @@ directive @relationship( *Configure aggregation* -From the previous type definitions, the type `Actor` produced is: +From the previous type definitions, the types related to `Actor` produced are: [source, graphql, indent=0] ---- type Actor { name: String! actedIn(where: MovieWhere, sort: [MovieSort!]!, limit: Int, offset: Int, directed: Boolean = true): [Movie!]! - actedInAggregate(where: MovieWhere, directed: Boolean = true): ActorMovieActedInAggregationSelection actedInConnection(where: ActorActedInConnectionWhere, first: Int, after: String, directed: Boolean = true, sort: [ActorActedInConnectionSort!]): ActorActedInConnection! } + +type ActorActedInConnection { + edges: [ActorActedInRelationship!]! + totalCount: Int! + pageInfo: PageInfo! + aggregate: ActorMovieActedInAggregateSelection! +} ---- -Note that the relationship field `actedIn` produces the operation field `actedInAggregate`, which allows aggregations on that relationship. +Note that the relationship field `actedIn` produces the operation field `aggregate` in the type `ActorActedInConnection`, which allows aggregations on that relationship. It is possible to configure this behavior by passing the argument aggregate on the `@relationship` directive: [source, graphql, indent=0] @@ -128,15 +131,14 @@ type Actor @node { } ---- -In this case, as the argument `aggregate` was passed as false, the type `Actor` produced is: +In this case, as the argument `aggregate` was passed as false, the type `ActorActedInConnection` produced is: [source, graphql, indent=0] ---- -type Actor { - name: String! - age: Int - actedIn(where: MovieWhere, sort: [MovieSort!]!, limit: Int, offset: Int, directed: Boolean = true): [Movie!]! - actedInConnection(where: ActorActedInConnectionWhere, first: Int, after: String, directed: Boolean = true, sort: [ActorActedInConnectionSort!]): ActorActedInConnection! +type ActorActedInConnection { + edges: [ActorActedInRelationship!]! + totalCount: Int! + pageInfo: PageInfo! } ---- @@ -155,7 +157,6 @@ enum NestedOperations { DELETE CONNECT DISCONNECT - CONNECT_OR_CREATE } ---- @@ -176,7 +177,7 @@ type Movie @node { type Actor @node { name: String! age: Int - actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, nestedOperations: [UPDATE, DELETE, CONNECT, DISCONNECT, CONNECT_OR_CREATE]) + actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, nestedOperations: [UPDATE, DELETE, CONNECT, DISCONNECT]) } ---- @@ -242,14 +243,13 @@ Aggregations, however, are available on both: [source, graphql, indent=0] ---- -type MovieAggregateSelection { - count: Int! - description: StringAggregateSelectionNullable! - title: StringAggregateSelectionNonNullable! +type MovieAggregateNode { + title: StringAggregateSelection! + description: StringAggregateSelection! } ---- -In case you want to remove the `description` field from `MovieAggregateSelection`, you need to change the `onAggregate` value to `false`: +In case you want to remove the `description` field from `MovieAggregateNode`, you need to change the `onAggregate` value to `false`: [source, graphql, indent=0] ---- @@ -270,7 +270,6 @@ From the previous type definitions, the type `Actor` produced is: type Actor { name: String! actedIn(where: MovieWhere, sort: [MovieSort!]!, limit: Int, offset: Int, directed: Boolean = true): [Movie!]! - actedInAggregate(where: MovieWhere, directed: Boolean = true): ActorMovieActedInAggregationSelection actedInConnection(where: ActorActedInConnectionWhere, first: Int, after: String, directed: Boolean = true, sort: [ActorActedInConnectionSort!]): ActorActedInConnection! } ---- @@ -300,12 +299,17 @@ Generate the type `Actor`: ---- type Actor { name: String! - actedInAggregate(where: MovieWhere, directed: Boolean = true): ActorMovieActedInAggregationSelection + actedInConnection(where: ActorActedInConnectionWhere, first: Int, after: String, directed: Boolean = true, sort: [ActorActedInConnectionSort!]): ActorActedInConnection! +} + +type ActorActedInConnection { + aggregate: ActorMovieActedInAggregateSelection! } ---- -Note how `actedInAggregate` is not affected by the argument `onAggregate`. -To disable the generation of `actedInAggregate`, see the `aggregate` argument of the directive xref::/schema-configuration/field-configuration.adoc#_relationship[`@relationship`]. +Note how the `aggregate` field is still present on the `ActorActedInConnection` type, and not affected by the argument `onAggregate`. + +To disable the generation of the `aggregate` field, see the `aggregate` argument of the xref::/schema-configuration/field-configuration.adoc#_relationship[`@relationship` directive]. == `@settable` @@ -351,7 +355,7 @@ input MovieCreateInput { } input MovieUpdateInput { - title: String + title: StringScalarMutations } ---- @@ -387,12 +391,12 @@ input ActorCreateInput { } input ActorUpdateInput { - name: String + name: StringScalarMutations actedIn: [ActorActedInUpdateFieldInput!] } ---- -This means `actedIn` can be updated on an update, but it is no longer available on `create`` operations. +This means `actedIn` can be updated on an update, but it is no longer available on `create` operations. == `@filterable` @@ -437,41 +441,17 @@ input MovieWhere { OR: [MovieWhere!] AND: [MovieWhere!] NOT: MovieWhere - title_EQ: String - title_IN: [String!] - title_CONTAINS: String - title_STARTS_WITH: String - title_ENDS_WITH: String - actorsAggregate: MovieActorsAggregateInput - actors_ALL: ActorWhere - actors_NONE: ActorWhere - actors_SINGLE: ActorWhere - actors_SOME: ActorWhere - actorsConnection_ALL: MovieActorsConnectionWhere - actorsConnection_NONE: MovieActorsConnectionWhere - actorsConnection_SINGLE: MovieActorsConnectionWhere - actorsConnection_SOME: MovieActorsConnectionWhere + title: StringScalarFilters + actors: ActorRelationshipFilters + actorsConnection: MovieActorsConnectionFilters } + input ActorActedInNodeAggregationWhereInput { AND: [ActorActedInNodeAggregationWhereInput!] OR: [ActorActedInNodeAggregationWhereInput!] NOT: ActorActedInNodeAggregationWhereInput - title_AVERAGE_LENGTH_EQUAL: Float - title_LONGEST_LENGTH_EQUAL: Int - title_SHORTEST_LENGTH_EQUAL: Int - title_AVERAGE_LENGTH_GT: Float - title_LONGEST_LENGTH_GT: Int - title_SHORTEST_LENGTH_GT: Int - title_AVERAGE_LENGTH_GTE: Float - title_LONGEST_LENGTH_GTE: Int - title_SHORTEST_LENGTH_GTE: Int - title_AVERAGE_LENGTH_LT: Float - title_LONGEST_LENGTH_LT: Int - title_SHORTEST_LENGTH_LT: Int - title_AVERAGE_LENGTH_LTE: Float - title_LONGEST_LENGTH_LTE: Int - title_SHORTEST_LENGTH_LTE: Int + title: StringScalarAggregationFilters } ---- @@ -507,33 +487,16 @@ input MovieWhere { OR: [MovieWhere!] AND: [MovieWhere!] NOT: MovieWhere - title_EQ: String - title_IN: [String!] - title_CONTAINS: String - title_STARTS_WITH: String - title_ENDS_WITH: String + title: StringScalarFilters } input ActorActedInNodeAggregationWhereInput { AND: [ActorActedInNodeAggregationWhereInput!] OR: [ActorActedInNodeAggregationWhereInput!] NOT: ActorActedInNodeAggregationWhereInput - title_AVERAGE_LENGTH_EQUAL: Float - title_LONGEST_LENGTH_EQUAL: Int - title_SHORTEST_LENGTH_EQUAL: Int - title_AVERAGE_LENGTH_GT: Float - title_LONGEST_LENGTH_GT: Int - title_SHORTEST_LENGTH_GT: Int - title_AVERAGE_LENGTH_GTE: Float - title_LONGEST_LENGTH_GTE: Int - title_SHORTEST_LENGTH_GTE: Int - title_AVERAGE_LENGTH_LT: Float - title_LONGEST_LENGTH_LT: Int - title_SHORTEST_LENGTH_LT: Int - title_AVERAGE_LENGTH_LTE: Float - title_LONGEST_LENGTH_LTE: Int - title_SHORTEST_LENGTH_LTE: Int + title: StringScalarAggregationFilters } + ---- As shown by the previous inputs fields, the `actors` field is not available for filtering on both value and aggregation filters. diff --git a/modules/ROOT/pages/directives/schema-configuration/global-configuration.adoc b/modules/ROOT/pages/directives/schema-configuration/global-configuration.adoc index 4fbcd1f5..d510addb 100644 --- a/modules/ROOT/pages/directives/schema-configuration/global-configuration.adoc +++ b/modules/ROOT/pages/directives/schema-configuration/global-configuration.adoc @@ -23,15 +23,29 @@ type Actor @node { extend schema @query(read: true, aggregate: false) ---- -**Query** +This configuration disables all top-level aggregation operations for the `Movie` and `Actor` types, while still allowing read operations. - * `movies` - * [.line-through]#`moviesAggregate`# - * `moviesConnection` - * `actors` - * [.line-through]#`actorsAggregate`# - * `actorsConnection` +[source, graphql, indent=0] +---- +type Query { + moviesConnection(first: Int, after: String, where: MovieWhere, sort: [MovieSort!]): MoviesConnection! + movies(where: MovieWhere, limit: Int, offset: Int, sort: [MovieSort!]): [Movie!]! + actorsConnection(first: Int, after: String, where: ActorWhere, sort: [ActorSort!]): ActorsConnection! + actors(where: ActorWhere, limit: Int, offset: Int, sort: [ActorSort!]): [Actor!]! +} +type MoviesConnection { + edges: [MovieEdge!]! + totalCount: Int! + pageInfo: PageInfo! +} + +type ActorsConnection { + edges: [ActorEdge!]! + totalCount: Int! + pageInfo: PageInfo! +} +---- **Invalid schema usage** diff --git a/modules/ROOT/pages/directives/schema-configuration/type-configuration.adoc b/modules/ROOT/pages/directives/schema-configuration/type-configuration.adoc index 93802430..06c0fc8d 100644 --- a/modules/ROOT/pages/directives/schema-configuration/type-configuration.adoc +++ b/modules/ROOT/pages/directives/schema-configuration/type-configuration.adoc @@ -20,7 +20,6 @@ From these type definitions, the library generates the following operation field **Query**: * `movies` - * `moviesAggregate` * `moviesConnection` **Mutation**: @@ -34,7 +33,12 @@ From these type definitions, the library generates the following operation field * `movieCreated` * `movieUpdated` * `movieDeleted` -. + +**moviesConnection**: + * `aggregate` + * `edges` + * `totalCount` + * `pageInfo` This page describes how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. @@ -51,7 +55,7 @@ directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | === Usage -.Disable _movies_ and _moviesConnection_ operations +.Disable query _movies_ and _moviesConnection_ edges field [source, graphql, indent=0] ---- type Movie @node @query(read: false, aggregate: true) { @@ -60,7 +64,7 @@ type Movie @node @query(read: false, aggregate: true) { } ---- -.Disable _moviesAggregate_ operations +.Disable _moviesConnection_ aggregate field [source, graphql, indent=0] ---- type Movie @node @query(read: true, aggregate: false) { @@ -108,7 +112,8 @@ type Movie @node @mutation(operations: [CREATE]) { == `@subscription` -This directive is used to limit subscription operations in the library. +This directive is used to enable subscription operations in the library. +Subscriptions are opt-in by default, meaning that they are disabled unless explicitly enabled. === Definition @@ -125,22 +130,29 @@ directive @subscription(events: [SubscriptionFields!]! = [CREATED, UPDATED, DELE === Usage -.Disable subscriptions for _Movie_ +.Enable only _movieCreated_ subscription for _Movie_ [source, graphql, indent=0] ---- -type Movie @node @subscription(events: []) { +type Movie @node @subscription(events: [CREATED]) { title: String length: Int } ---- -.Enable only _movieCreated_ subscription for _Movie_ +.Enable subscriptions for all types except for _Movie_ [source, graphql, indent=0] ---- -type Movie @node @subscription(events: [CREATED]) { +type Movie @node @subscription(events: []) { title: String length: Int } + +type Actor @node { + name: String + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) +} + +extend schema @subscription ---- [[type-definitions-default-values-default]] diff --git a/modules/ROOT/pages/filtering.adoc b/modules/ROOT/pages/filtering.adoc index 55446de6..e491f991 100644 --- a/modules/ROOT/pages/filtering.adoc +++ b/modules/ROOT/pages/filtering.adoc @@ -23,28 +23,28 @@ For example, if you want to match all actors either by the name of "Keanu" or no [source, graphql, indent=0] ---- query { - actors(where: { - AND: [ - { - OR: [ - { name_CONTAINS: "Keanu" }, - { NOT: { name_ENDS_WITH: "Pantoliano" } } - ] - }, - { - movies_SOME: { title_EQ: "The Matrix" } - } - ]} - ) { - name - movies { - title + actors( + where: { + AND: [ + { + OR: [ + { name: { contains: "Keanu" } } + { NOT: { name: { endsWith: "Pantoliano" } } } + ] } + { movies: { some: { title: { eq: "The Matrix" } } } } + ] + } + ) { + name + movies { + title } + } } ---- -`name_CONTAINS` and `name_ENDS_WITH` are xref:#_string_comparison[String comparisons] while `movies_SOME` is a xref:#_relationship_filtering[relationship filter]. +`contains` and `endsWith` are xref:#_string_comparison[String comparisons] while `some` is a xref:#_relationship_filtering[relationship filter]. === Equality operators @@ -56,10 +56,11 @@ For example: [source, graphql, indent=0] ---- query { - users(where: { name_EQ: "John" }) + users(where: { NOT: { name: { eq: "John" } } }) { id name } +} ---- For non-equality, you must use the xref:#_boolean_operators[`NOT`] logical operator. @@ -68,10 +69,11 @@ For non-equality, you must use the xref:#_boolean_operators[`NOT`] logical opera [source, graphql, indent=0] ---- query { - users(where: { NOT: { name_EQ: "John" }}) + users(where: { NOT: { name: { eq: "John" } } }) { id name } +} ---- [NOTE] @@ -83,10 +85,10 @@ For the `Boolean` type, equality operators are the only ones available. These are the operators available for numeric (`Int`, `Float`, xref::/types/scalar.adoc[`BigInt`]), xref::/types/temporal.adoc[temporal] and xref::/types/spatial.adoc[spatial] types: -* `_LT` -* `_LTE` -* `_GT` -* `_GTE` +* `lt` +* `lte` +* `gt` +* `gte` Here is an example of how to use them: @@ -94,7 +96,7 @@ Here is an example of how to use them: [source, graphql, indent=0] ---- query { - users(where: {age_LT: 50 }) { + users(where: { age: { lt: 50 } }) { id name age @@ -105,62 +107,67 @@ query { Spatial types use numerical filtering differently and they also have additional options. See xref:filtering.adoc#_spatial_type_filtering[] for more information. -==== Spatial type filtering - -Both the `Point` and the `CartesianPoint` types use xref:#_numerical_operators[numerical operators] and have an additional `_DISTANCE` filter. -Here is a list of what each filter does for the two types: - -* `_LT`: checks if a point is less than the distance in the `distance` field away (in meters) from the point specified by the `point` field. -* `_LTE`: checks if a point is less than or equal to the distance in the `distance` field away (in meters) from the point specified by the `point` field. -* `_DISTANCE`: checks if a point is the exact distance in the `distance` field away (in meters) from the point specified by the `point` field. -* `_GT`: checks if a point is greater than the distance in the `distance` field away (in meters) from the point specified by the `point` field. -* `_GTE`: checks if a point is greater than or equal to the distance in the `distance` field away (in meters) from the point specified by the `point` field. +=== Spatial type filtering -For a `Point` type, all filters take the following type as an argument: +Spatial filters are available for both Point and Cartesian fields. +They allow you to filter spatial data either by exact equality or based on a distance criterion. +For exact matching, use the eq operator: [source, graphql, indent=0] ---- -input PointDistance { - point: Point! - distance: Float! -} ----- - -In practice, you can construct queries like the following, which finds all users within a 5km (5000m) radius of a `Point`: - -[source, graphql, indent=0] ----- -query CloseByUsers($longitude: Float!, $latitude: Float!) { - users(where: { location_LTE: { point: { longitude: $longitude, latitude: $latitude }, distance: 5000 } }) { - name - location { - longitude - latitude - } +query { + users( + where: { + location: { + eq: { longitude: 9, latitude: 10, height: 11 } + } + } + ) { + name + location { + longitude + latitude } + } } ---- -Similarly, for a `CartesianPoint` type, all filters take the following type as an argument: - +For distance-based filtering, combine xref:#_numerical_operators[numerical operators] operators with the distance operator: [source, graphql, indent=0] ---- -input CartesianPointDistance { - point: CartesianPoint! - distance: Float! +query CloseByUsers { + users( + where: { + location: { + distance: { lte: 5000, from: { longitude: 9, latitude: 10, height: 11 } } + } + } + ) { + name + location { + longitude + latitude + } + } } ---- -The same query for a `CartesianPoint`: - +For Cartesian points, use: [source, graphql, indent=0] ---- -query CloseByUsers($x: Float!, $y: Float!) { - users(where: { location_LTE: { point: { x: $x, y: $y }, distance: 5000 } }) { +query CloseByUsers { + users( + where: { + location: { + distance: { lte: 5000, from: { x: 9, y: 10, z: 11 } } + } + } + ) { name location { x y + z } } } @@ -172,9 +179,9 @@ query CloseByUsers($x: Float!, $y: Float!) { The following case-sensitive comparison operators are available for `String` and `ID` types: -* `_STARTS_WITH` -* `_ENDS_WITH` -* `_CONTAINS` +* `startsWith` +* `endsWith` +* `contains` Here is an example of how to use them: @@ -182,7 +189,7 @@ Here is an example of how to use them: [source, graphql, indent=0] ---- query { - users(where: { name_STARTS_WITH: "J" }) { + users(where: { name: { startsWith: "J" } }) { id name } @@ -225,7 +232,7 @@ const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); === RegEx matching -The filter `_MATCHES` is available for comparison of `String` and `ID` types. +The filter `matches` is available for comparison of `String` and `ID` types. It accepts RegEx strings as an argument and returns any matches. Note that RegEx matching filters are disabled by default. @@ -303,12 +310,12 @@ type Actor @node { } ---- -The `_IN` operator is available on non-array fields, and accepts an array argument: +The `in` operator is available on non-array fields, and accepts an array argument: [source, graphql, indent=0] ---- query { - movies(where: { year_IN: [1999, 2000, 2001] }) { + movies(where: { year: { in: [1999, 2000, 2001] } }) { title year } @@ -317,12 +324,12 @@ query { The query returns all movies released in the years 1999, 2000 and 2001. -Conversely, the `_INCLUDES` operator is available on array fields, and accepts a single argument: +Conversely, the `includes` operator is available on array fields, and accepts a single argument: [source, graphql, indent=0] ---- query { - movies(where: { genres_INCLUDES: "Action" }) { + movies(where: { genres: { includes: "Action" } }) { title genres } @@ -331,23 +338,20 @@ query { The query returns all movies which have "Action" as one of their genres. -`_IN` and `_INCLUDES` are available for all types except `Boolean`. +`in` and `includes` are available for all types except `Boolean`. == Interface filtering -You can use the `typename_IN` filter to filter interfaces. +You can use the `typename` filter to filter interfaces. Refer to xref:types/interfaces.adoc#type-definitions-interfaced-types-querying[Type definitions -> Type -> Interface] for more details and an example. == Relationship filtering -Relationship filtering depends on the type of relationship: - -* `n..1`: the filtering is done on equality or inequality of the related nodes by specifying a filter on `field`. -* `n..m`: the filtering is done on the list of related nodes and is based on the https://neo4j.com/docs/cypher-manual/current/functions/predicate/[list predicates] available in Cypher: -** `field_ALL` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-all[all] -** `field_NONE` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-none[none] -** `field_SOME` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-any[any] -** `field_SINGLE` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-single[single] +The filtering is done on the list of related nodes and is based on the https://neo4j.com/docs/cypher-manual/current/functions/predicate/[list predicates] available in Cypher: +** `all` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-all[all] +** `none` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-none[none] +** `some` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-any[any] +** `single` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-single[single] For example, take these type definitions: @@ -362,41 +366,11 @@ type User @node { type Post @node { id: ID! content: String - author: User! @relationship(type: "HAS_POST", direction: IN) likes: [User!]! @relationship(type: "LIKES", direction: IN) } ---- -=== `n..1` relationships - -In the type definitions example, an `author` represents an `n..1` relationship on `Post`, where a given `Post` is authored by one, and only one, `author`. -The available filter is `author`. - -For example: - -.Find all posts by a desired author -[source, graphql, indent=0] ----- -query { - posts(where: { author: { id_EQ: "7CF1D9D6-E527-4ACD-9C2A-207AE0F5CB8C" } }) { - content - } -} ----- - -.Find all posts `NOT` by an undesired author -[source, graphql, indent=0] ----- -query { - posts(where: { NOT: { author: { id_EQ: "7CF1D9D6-E527-4ACD-9C2A-207AE0F5CB8C" } } }) { - content - } -} ----- - -=== `n..m` relationships - -In the type definitions example, `posts` represents an `n..m` relationship on `User`, where a given `User` can have any number of `posts`. +In the type definitions example, `posts` represents a relationship on `User`, where a given `User` can have any number of `posts`. For example: @@ -404,7 +378,7 @@ For example: [source, graphql, indent=0] ---- query { - users(where: { posts_ALL: { content_CONTAINS: "neo4j" } }) { + users(where: { posts: { all: { content: { contains: "neo4j" } } } }) { name } } @@ -414,7 +388,7 @@ query { [source, graphql, indent=0] ---- query { - users(where: { posts_NONE: { content_CONTAINS: "cypher" } }) { + users(where: { posts: { none: { content: { contains: "cypher" } } } }) { name } } @@ -424,7 +398,7 @@ query { [source, graphql, indent=0] ---- query { - users(where: { posts_SOME: { content_CONTAINS: "graphql" } }) { + users(where: { posts: { some: { content: { contains: "graphql" } } } }) { name } } @@ -434,7 +408,7 @@ query { [source, graphql, indent=0] ---- query { - users(where: { posts_SINGLE: { content_CONTAINS: "graph" } }) { + users(where: { posts: { single: { content: { contains: "graph" } } } }) { name } } @@ -466,9 +440,11 @@ type Post @node { [source, graphql, indent=0] ---- query { - posts(where: { likesAggregate: { count_GT: 5 } }) { - content - } + posts( + where: { likesConnection: { aggregate: { count: { nodes: { gt: 5 } } } } } + ) { + content + } } ---- @@ -492,9 +468,15 @@ type Flight @node { [source, graphql, indent=0] ---- query { - flights(where: { passengersAggregate: { node: { age_AVERAGE_GTE: 18 } } }) { - code + flights( + where: { + passengersConnection: { + aggregate: { node: { age: { average: { gt: 18 } } } } + } } + ) { + code + } } ---- @@ -521,9 +503,15 @@ type ActedIn @relationshipProperties { [source, graphql, indent=0] ---- query { - movies(where: { actorsAggregate: { edge: { screenTime_MIN_LT: 10 } } }) { - title + movies( + where: { + actorsConnection: { + aggregate: { edge: { screenTime: { min: { lt: 10 } } } } + } } + ) { + title + } } ---- @@ -538,46 +526,60 @@ They provide autogenerated filters available for each type on the `node` and `ed | `count` | A special 'top level' key inside the `where` aggregation and will be available for all relationships. This is used to count the amount of relationships the parent node is connected to. -| `count_EQUAL`, `count_GT`, `count_GTE`, `count_LT`, `count_LTE` +| `nodes`, `edges` a| [source, graphql, indent=0] ---- query { - posts(where: { likesAggregate: { count_GT: 5 } }) { - content - } + posts( + where: { likesConnection: { aggregate: { count: { nodes: { gt: 5 } } } } } + ) { + content + } } ---- | `String` | These operators are calculated against the length of each string. -| `_AVERAGE_LENGTH_EQUAL` `_AVERAGE_LENGTH_GT` `_AVERAGE_LENGTH_GTE` `_AVERAGE_LENGTH_LT` `_AVERAGE_LENGTH_LTE` `_SHORTEST_LENGTH_EQUAL` `_SHORTEST_LENGTH_GT` `_SHORTEST_LENGTH_GTE` `_SHORTEST_LENGTH_LT` `_SHORTEST_LENGTH_LTE` `_LONGEST_LENGTH_EQUAL` `_LONGEST_LENGTH_GT` `_LONGEST_LENGTH_GTE` `_LONGEST_LENGTH_LT` `_LONGEST_LENGTH_LTE` +| `averageLength` `shortestLength` `longestLength` a| [source, graphql, indent=0] ---- query { - posts(where: { likesAggregate: { node: { name_LONGEST_LENGTH_GT: 5 } } }) { - content + posts( + where: { + likesConnection: { + aggregate: { node: { name: { longestLength: { gt: 5 } } } } + } } + ) { + content + } } ---- | `Numerical` | Used in the case of `Int`, `Float`, and `BigInt`. -| `_AVERAGE_EQUAL`, `_AVERAGE_GT`, `_AVERAGE_GTE`, `_AVERAGE_LT`, `_AVERAGE_LTE`, `_SUM_EQUAL`, `_SUM_GT`, `_SUM_GTE`, `_SUM_LT`, `_SUM_LTE`, `_MIN_EQUAL`, `_MIN_GT`, `_MIN_GTE`, `_MIN_LT`, `_MIN_LTE`, `_MAX_EQUAL`, `_MAX_GT`, `_MAX_GTE`, `_MAX_LT`, `_MAX_LTE` +| `average`, `min`, `max`, `sum` a| [source, graphql, indent=0] ---- query { - movies(where: { actorsAggregate: { edge: { screenTime_MIN_LT: 10 } } }) { - title + movies( + where: { + actorsConnection: { + aggregate: { edge: { screenTime: { min: { lt: 10 } } } } + } } + ) { + title + } } ---- | `Temporal` | Used in the case of `DateTime`, `LocalDateTime`, `LocalTime`, `Time`, and `Duration`. -| `_MIN_EQUAL`, `_MIN_GT`, `_MIN_GTE`, `_MIN_LT`, `_MIN_LTE`, `_MAX_EQUAL`, `_MAX_GT`, `_MAX_GTE`, `_MAX_LT`, `_MAX_LTE` +| `min`, `max` a| .Type definitions [source, graphql, indent=0] @@ -592,7 +594,7 @@ type Event @node { [source, graphql, indent=0] ---- query EventsAggregate { - users(where: { eventsAggregate: { node: { startTime_GT: "2022-08-14T15:00:00Z" } } }) { + users(where: { eventsConnection: { aggregate: { node: { startTime: { gt:"2022-08-14T15:00:00Z" } } } } }) { name } } @@ -600,7 +602,7 @@ query EventsAggregate { | `Duration` | Description. -| `_AVERAGE_EQUAL`, `_AVERAGE_GT`, `_AVERAGE_GTE`, `_AVERAGE_LT`, `_AVERAGE_LTE` +| `average` a| .Type definitions [source, graphql, indent=0] @@ -615,7 +617,7 @@ type Event @node { [source, graphql, indent=0] ---- query EventsAggregate { - users(where: { eventsAggregate: { node: { duration_AVERAGE_LT: "PT2H" } } }) { + users(where: { eventsConnection: { aggregate: { node: { duration: { average: { lt: "PT2H" } } } } } }) { name } } diff --git a/modules/ROOT/pages/mutations/create.adoc b/modules/ROOT/pages/mutations/create.adoc index dd8f87b2..61c95de2 100644 --- a/modules/ROOT/pages/mutations/create.adoc +++ b/modules/ROOT/pages/mutations/create.adoc @@ -104,58 +104,7 @@ mutation { This creates a `User` with name "John Doe" and an introductory post. Both are returned with their autogenerated IDs. -[NOTE] -==== -You can perform similar and complementary actions by using the `update` mutation combined with `create`. -Read about xref:mutations/update.adoc#_connectorcreate_relationships[`update`] for more information. -==== - -== `connectOrCreate` relationships - -If a related node has the `@unique` directive defined, you can use `connectOrCreate` nested in a `create` mutation to perform an operation similar to a link:https://neo4j.com/docs/cypher-manual/current/clauses/merge/[Cypher `MERGE`] operation on the related node. -This will create a new relationship and the related node if it doesn't exist yet. - -Consider the following type definitions: - -[source, graphql, indent=0] ----- -type Actor @node { - name: String! - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) -} - -type Movie @node { - title: String - id: ID! @id @unique - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} ----- - -Since a movie ID is unique, you can use `connectOrCreate` in an `Actor` mutation to ensure the movie exists in the database before connecting them. -Note that only `@unique` or `@id` fields can be used in `where`: - -[source, graphql, indent=0] ----- -mutation { - createActors(input: { - name: "Tom Hanks", - movies: { - connectOrCreate: { - where: { node: { id_EQ: "1234" } } - onCreate: { node: { title: "Forrest Gump" } } - } - } - }) { - info { - nodesCreated - } - } -} ----- -This ensures that a movie with ID 1234 exists and is connected to `"Tom Hanks"`. -If the movie does not exist, it will be created with the title `"Forrest Gump"`. -If a movie with the given ID already exists, it will also be connected to `"Tom Hanks"`, and keep whatever title it has. == `create` optimization @@ -165,5 +114,5 @@ However, there is a known performance issue for large batch sizes. The Neo4j GraphQL Library contains an optimization feature designed to mitigate it, but it does not work in the following scenarios: * A field is populated using the directive `@populated_by`. -* The `connect` or `connectOrCreate` operations are used. +* The `connect` operations are used. * Interface and union types are present in the mutation. diff --git a/modules/ROOT/pages/mutations/delete.adoc b/modules/ROOT/pages/mutations/delete.adoc index 29e33d0d..cba49d6c 100644 --- a/modules/ROOT/pages/mutations/delete.adoc +++ b/modules/ROOT/pages/mutations/delete.adoc @@ -49,7 +49,7 @@ You can delete a single post by executing the following GraphQL statement: ---- mutation { deletePosts(where: { - id_EQ: "6042E807-47AE-4857-B7FE-1AADF522DE8B" + id: { eq: "6042E807-47AE-4857-B7FE-1AADF522DE8B" } }) { nodesDeleted relationshipsDeleted @@ -67,7 +67,7 @@ In case you want to delete a `User` and all of their posts, you can use a single [source, graphql, indent=0] ---- mutation { - deleteUsers(where: { name_EQ: "Jane Doe" }, delete: { posts: {} }) { + deleteUsers(where: { name: { eq: "Jane Doe" } }, delete: { posts: {} }) { nodesDeleted relationshipsDeleted } diff --git a/modules/ROOT/pages/mutations/update.adoc b/modules/ROOT/pages/mutations/update.adoc index 4dd4b7c8..b24052a9 100644 --- a/modules/ROOT/pages/mutations/update.adoc +++ b/modules/ROOT/pages/mutations/update.adoc @@ -12,7 +12,7 @@ Consider the following type definitions: type Post @node { id: ID! @id content: String! - creator: User! @relationship(type: "HAS_POST", direction: IN) + creator: [User!]! @relationship(type: "HAS_POST", direction: IN) } type User @node { @@ -58,18 +58,14 @@ You can update the content of a `Post` by executing the following GraphQL statem [source, graphql, indent=0] ---- mutation { - updatePosts( - where: { - id_EQ: "892CC104-A228-4BB3-8640-6ADC9F2C2A5F" - } - update: { - content: "Some new content for this Post!" - } - ) { - posts { - content - } + updatePosts( + where: { id: { eq: "892CC104-A228-4BB3-8640-6ADC9F2C2A5F" } } + update: { content: { set: "Some new content for this Post!" } } + ) { + posts { + content } + } } ---- @@ -83,7 +79,7 @@ Instead of creating a `Post` with the `create` mutation and then connecting it t ---- mutation { updateUsers( - where: { name_EQ: "John Doe" } + where: { name: { eq: "John Doe" } } update: { posts: { create: [ @@ -103,53 +99,6 @@ mutation { } ---- -== `connectOrCreate` relationships - -Consider the example provided in the xref:mutations/create.adoc#_connectorcreate_relationships[`create`] page: - -[source, graphql, indent=0] ----- -mutation { - createActors(input: { - name: "Tom Hanks", - movies: { - connectOrCreate: { - where: { node: { id_EQ: "1234" } } - onCreate: { node: { title: "Forrest Gump" } } - } - } - }) { - info { - nodesCreated - } - } -} ----- - -`connectOrCreate` is also available in `update` operations: - -[source, graphql, indent=0] ----- -mutation { - updateActors( - update: { - movies: { - connectOrCreate: { - where: { node: { id_EQ: "1234" } } - onCreate: { node: { title: "Forrest Gump" } } - } - } - }, - where: { name_EQ: "Tom Hanks" } - ) { - info { - nodesCreated - } - } -} ----- - -This operation is equivalent to the previous example. == Array methods @@ -161,12 +110,12 @@ Array methods allow the modification of existing property arrays in `update` mut For that, the following operators are available: -* `_PUSH` -* `_POP` +* `push` +* `pop` -=== `_PUSH` +=== `push` -`_PUSH` conforms to the type of input defined in the type definition. +`push` conforms to the type of input defined in the type definition. Consider the following type definitions, a `Movie` with a property array of `String` types called `tags`: @@ -174,18 +123,18 @@ Consider the following type definitions, a `Movie` with a property array of `Str ---- type Movie @node { title: String - tags: [String] + tags: [String!] } ---- You can push tags to the `tags` property array: -.Mutation with a single `_PUSH` +.Mutation with a single `push` ==== [source, graphql, indent=0] ---- mutation { - updateMovies (update: { tags_PUSH: "another tag" }) { + updateMovies (update: { tags: { push: "another tag" } }) { movies { title tags @@ -205,12 +154,12 @@ mutation { Or push multiple elements in a single update: -.Mutation with two `_PUSH` +.Mutation with two `push` ==== [source, graphql, indent=0] ---- mutation { - updateMovies (update: { tags_PUSH: ["another tag", "one more tag"] }) { + updateMovies (update: { tags: { push: ["another tag", "one more tag"] } }) { movies { title tags @@ -234,8 +183,8 @@ Similarly, you can have multiple array property fields and update them in the sa ---- type Movie @node { title: String - tags: [String] - moreTags: [String] + tags: [String!] + moreTags: [String!] } ---- @@ -245,7 +194,7 @@ You can also push to both the `tags` and `moreTags` property arrays: [source, graphql, indent=0] ---- mutation { - updateMovies (update: { tags_PUSH: "another tag", moreTags_PUSH: "a different tag" }) { + updateMovies (update: { tags: { push: "another tag" }, moreTags: { push: "a different tag" } }) { movies { title tags @@ -282,7 +231,7 @@ Consider the following type definitions, a `Movie` with a property array called ---- type Movie @node { title: String - tags: [String] + tags: [String!] } ---- @@ -293,7 +242,7 @@ You can pop from this `tags` property array: [source, graphql, indent=0] ---- mutation { - updateMovies (update: { tags_POP: 1 }) { + updateMovies (update: { tags: { pop: 1 } }) { movies { title tags @@ -324,12 +273,12 @@ Or, for more than one property from the array: [source, graphql, indent=0] ---- mutation { - updateMovies (update: { tags_POP: 2 }) { - movies { - title - tags - } + updateMovies(update: { tags: { pop: 2 } }) { + movies { + title + tags } + } } ---- @@ -355,8 +304,8 @@ Similarly, you can have multiple array property fields and update them in the sa ---- type Movie @node { title: String - tags: [String] - moreTags: [String] + tags: [String!] + moreTags: [String!] } ---- @@ -367,13 +316,13 @@ Then, you can pop from both the `tags` and `moreTags` property arrays: [source, graphql, indent=0] ---- mutation { - updateMovies (update: { tags_POP: 1, moreTags_POP: 2 }) { - movies { - title - tags - moreTags - } + updateMovies(update: { tags: { pop: 1 }, moreTags: { pop: 2 } }) { + movies { + title + tags + moreTags } + } } ---- @@ -404,17 +353,15 @@ They are supported within these entities: * Relationship properties * Interfaces -For the `Int` and `BigInt` types, the following operators are available: +For the `Int`. `Float` and `BigInt` types, the following operators are available: -* `_INCREMENT` -* `_DECREMENT` +* `add` +* `subtract` -For the `Float` type, the following operators are available: +For the `Float` type these additional operators are also available: -* `_ADD` -* `_SUBTRACT` -* `_MULTIPLY` -* `_DIVIDE` +* `multiply` +* `divide` [NOTE] ==== @@ -429,7 +376,7 @@ For example, consider the following GraphQL schema for a social video platform: type Video @node { id: ID @id views: Int - ownedBy: User @relationship(type: "OWN_VIDEO", properties: "OwnVideo", direction: IN) + ownedBy: [User!]! @relationship(type: "OWN_VIDEO", properties: "OwnVideo", direction: IN) } type User @node { @@ -449,8 +396,8 @@ Here is how you can do that: ---- mutation incrementViewCountMutation { updateVideos( - where: { id_EQ: "VideoID" } - update: { views_INCREMENT: 1 } + where: { id: { eq: "VideoID" } } + update: { views: { add: 1 } } ) { videos { id @@ -467,14 +414,16 @@ To do that, you have to update the relationship property `revenue`: ---- mutation addRevenueMutation { updateUsers( - where: { id_EQ: "UserID" }, - update: { ownVideo: [{ update: { edge: { revenue_ADD: 0.01 } } }] } + where: { id: { eq: "UserID" } } + update: { ownVideo: [{ update: { edge: { revenue: { add: 0.01 } } } }] } ) { users { id ownVideoConnection { edges { - revenue + properties { + revenue + } } } } diff --git a/modules/ROOT/pages/neo4jgraphql-class.adoc b/modules/ROOT/pages/neo4jgraphql-class.adoc index 87ca2525..18336568 100644 --- a/modules/ROOT/pages/neo4jgraphql-class.adoc +++ b/modules/ROOT/pages/neo4jgraphql-class.adoc @@ -37,12 +37,11 @@ export type Neo4jFeaturesSettings = { * NOTE: this will not remove user defined deprecated fields **/ excludeDeprecatedFields?: { - bookmark?: boolean; - negationFilters?: boolean; - arrayFilters?: boolean; - stringAggregation?: boolean; + mutationOperations?: boolean; aggregationFilters?: boolean; - nestedUpdateOperationsFields?: boolean; + aggregationFiltersOutsideConnection?: boolean; + relationshipFilters?: boolean; + attributeFilters?: boolean; }; vector?: Neo4jVectorSettings; }; @@ -71,7 +70,7 @@ See xref:security/configuration.adoc[Security > Configuration]. === Subscription settings Enable the `subscriptions` feature of the Neo4j GraphQL Library by setting it to `true`. -See xref:subscriptions/engines.adoc[Subscription engines]. +See xref:subscriptions/getting-started.adoc[Subscription]. === Exclude deprecated fields diff --git a/modules/ROOT/pages/optimization.adoc b/modules/ROOT/pages/optimization.adoc index 421eafe8..15deb25b 100644 --- a/modules/ROOT/pages/optimization.adoc +++ b/modules/ROOT/pages/optimization.adoc @@ -23,13 +23,13 @@ const neoSchema = new Neo4jGraphQL({ driver, features: { excludeDeprecatedFields: { - implicitEqualFilters: true; - deprecatedOptionsArgument: true; - directedArgument: true; - }, + mutationOperations: true; + aggregationFilters: true; + aggregationFiltersOutsideConnection: true; + relationshipFilters: true; + attributeFilters: true; + }; }, }); ``` -; - diff --git a/modules/ROOT/pages/queries-aggregations/aggregations.adoc b/modules/ROOT/pages/queries-aggregations/aggregations.adoc index 56682f57..14a28f64 100644 --- a/modules/ROOT/pages/queries-aggregations/aggregations.adoc +++ b/modules/ROOT/pages/queries-aggregations/aggregations.adoc @@ -11,7 +11,7 @@ Queries on this page assume the following type definitions: type Post @node { id: ID! @id content: String! - creator: User! @relationship(type: "HAS_POST", direction: IN, properties: "PostedAt") + creators: [User!]! @relationship(type: "HAS_POST", direction: IN, properties: "PostedAt") createdAt: DateTime! } @@ -35,7 +35,6 @@ For which the following query fields are generated: type Query { posts(where: PostWhere, sort: [PostSort!]!, limit: Int, offset: Int,): [Post!]! postsConnection(after: String, first: Int, sort: [PostSort], where: PostWhere): PostsConnection! - users(where: UserWhere, sort: [UserSort!]!, limit: Int, offset: Int,): [User!]! usersConnection(after: String, first: Int, sort: [UserSort], where: UserWhere): UsersConnection! } @@ -123,7 +122,7 @@ query { [source, graphql, indent=0] ---- query { - usersConnection(where: { name_STARTS_WITH: "J" }) { + usersConnection(where: { name: { startsWith: "J" } }) { aggregate { count { nodes @@ -172,7 +171,7 @@ query { aggregate { node { content { - longest + longest } } } diff --git a/modules/ROOT/pages/queries-aggregations/pagination.adoc b/modules/ROOT/pages/queries-aggregations/pagination.adoc index ec891936..afc88971 100644 --- a/modules/ROOT/pages/queries-aggregations/pagination.adoc +++ b/modules/ROOT/pages/queries-aggregations/pagination.adoc @@ -77,14 +77,12 @@ You can also fetch a `User` and then paginate through their posts: [source, graphql, indent=0] ---- query { - users(where: { - name_EQ: "Billy" - }) { - name - posts(offset: 20, limit: 10) { - content - } + users(where: { name: { eq: "Billy" } }) { + name + posts(offset: 20, limit: 10) { + content } + } } ---- @@ -113,21 +111,22 @@ If you wanted to fetch the posts of user "John Smith" 10 at a time, you would fi [source, graphql, indent=0] ---- query { - users(where: { name_EQ: "John Smith" }) { - name - postsConnection(first: 10) { - edges { - node { - content - } - } - pageInfo { - endCursor - hasNextPage - } + users(where: { name: { eq: "John Smith" } }) { + name + postsConnection(first: 10) { + edges { + node { + content } + } + pageInfo { + endCursor + hasNextPage + } } + } } + ---- In the return value, if `hasNextPage` is `true`, you would pass `endCursor` into the next query of 10. @@ -136,7 +135,7 @@ You can do this with a variable, for example, `$after` in the following query: [source, graphql, indent=0] ---- query Users($after: String) { - users(where: { name_EQ: "John Smith" }) { + users(where: { name: { eq: "John Smith"} }) { name postsConnection(first: 10, after: $after) { edges { @@ -165,21 +164,21 @@ Add the `totalCount` field which returns the total number of results matching th [source, graphql, indent=0] ---- -query Users($after: String) { - users(where: { name_EQ: "John Smith" }) { - name - postsConnection(first: 10) { - edges { - node { - content - } - } - pageInfo { - endCursor - hasNextPage - } - totalCount +query Users { + users(where: { name: { eq: "John Smith" } }) { + name + postsConnection(first: 10) { + edges { + node { + content } + } + pageInfo { + endCursor + hasNextPage + } + totalCount } + } } ---- diff --git a/modules/ROOT/pages/queries-aggregations/queries.adoc b/modules/ROOT/pages/queries-aggregations/queries.adoc index 0f444155..86519901 100644 --- a/modules/ROOT/pages/queries-aggregations/queries.adoc +++ b/modules/ROOT/pages/queries-aggregations/queries.adoc @@ -4,14 +4,14 @@ == Type definitions -Quries on this page assume the following type definitions: +Queries on this page assume the following type definitions: [source, graphql, indent=0] ---- type Post @node { id: ID! @id content: String! - creator: User! @relationship(type: "HAS_POST", direction: IN, properties: "PostedAt") + creators: [User!]! @relationship(type: "HAS_POST", direction: IN, properties: "PostedAt") createdAt: DateTime! } @@ -33,11 +33,10 @@ For which the following query fields are generated: [source, graphql, indent=0] ---- type Query { - posts(where: PostWhere, sort: [PostSort!]!, limit: Int, offset: Int,): [Post!]! - postsAggregate(where: PostWhere): PostAggregationSelection! - - users(where: UserWhere, sort: [UserSort!]!, limit: Int, offset: Int,): [User!]! - usersAggregate(where: UserWhere): UserAggregationSelection! + postsConnection(first: Int, after: String, where: PostWhere, sort: [PostSort!]): PostsConnection! + posts(where: PostWhere, limit: Int, offset: Int, sort: [PostSort!]): [Post!]! + usersConnection(first: Int, after: String, where: UserWhere, sort: [UserSort!]): UsersConnection! + users(where: UserWhere, limit: Int, offset: Int, sort: [UserSort!]): [User!]! } ---- @@ -60,12 +59,12 @@ query { [source, graphql, indent=0] ---- query { - users(where: { name_EQ: "Jane Smith" }) { - id - name - posts { - content - } + users(where: { name: { eq: "Jane Smith" } }) { + id + name + posts { + content } + } } ---- diff --git a/modules/ROOT/pages/queries-aggregations/sorting.adoc b/modules/ROOT/pages/queries-aggregations/sorting.adoc index 1b5797ce..a8c52101 100644 --- a/modules/ROOT/pages/queries-aggregations/sorting.adoc +++ b/modules/ROOT/pages/queries-aggregations/sorting.adoc @@ -13,10 +13,16 @@ Using this example type definition: type Movie @node { title: String! runtime: Int! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) +} + +type Actor @node { + surname: String! + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } ---- -The following sorting input type and query are generated: +The following sorting input type and query are generated for the type `Movie`: [source, graphql, indent=0] ---- @@ -36,7 +42,7 @@ input MovieSort { } type Query { - movies(where: MovieWhere, sort: [MovieSort!], limit: Int, offset: Int): [Movie!]! + movies(where: MovieWhere, limit: Int, offset: Int, sort: [MovieSort!]): [Movie!]! } ---- @@ -45,15 +51,10 @@ The following query fetches all movies sorted by runtime in ascending order: [source, graphql, indent=0] ---- query { - movies(sort: [ - { - runtime: ASC - } - ] - ) { - title - runtime - } + movies(sort: [{ runtime: ASC }]) { + title + runtime + } } ---- @@ -62,19 +63,15 @@ If there is a relationship between the `Movie` and an `Actor` type, you can also [source, graphql, indent=0] ---- query { - movies { - title - runtime - actors(sort: [ - { - surname: ASC - } - ] - ) { - surname - } + movies { + title + runtime + actors(sort: [{ surname: ASC }]) { + surname } + } } + ---- [CAUTION] diff --git a/modules/ROOT/pages/security/authentication.adoc b/modules/ROOT/pages/security/authentication.adoc index 2742387e..01895109 100644 --- a/modules/ROOT/pages/security/authentication.adoc +++ b/modules/ROOT/pages/security/authentication.adoc @@ -74,11 +74,11 @@ type User @authentication @node { With this configuration, authentication is validated when any of the following operations are _attempted_: -* *Create*: `createUsers` mutation, `create`, or `connectOrCreate` nested operation via a related type. -* *Read*: `users`, `usersConnection`, `usersAggregate` query, or access via related type. +* *Create*: `createUsers` mutation, `create` nested operation via a related type. +* *Read*: `users`, `usersConnection`, `aggregate` query, or access via related type. * *Update*: `updateUsers` mutation or `update` nested operation via a related type. * *Delete*: `deleteUsers` mutation or `delete` nested operation via a related type. -* *Create relationship*: `connect` or `connectOrCreate` nested operation via a related type. +* *Create relationship*: `connect` nested operation via a related type. * *Delete relationship*: `disconnect` nested operation via a related type. * *Subscribe*: all subscription operations related to type `User`. @@ -109,7 +109,7 @@ For instance, if it was a requirement that only users with the `admin` role can [source, graphql, indent=0] ---- -type User @authentication(operations: [DELETE], jwt: { roles_INCLUDES: "admin" }) @node { +type User @authentication(operations: [DELETE], jwt: { roles: { includes: "admin" }}) @node { id: ID! name: String! password: String! diff --git a/modules/ROOT/pages/security/authorization.adoc b/modules/ROOT/pages/security/authorization.adoc index 930ab609..a2a367c8 100644 --- a/modules/ROOT/pages/security/authorization.adoc +++ b/modules/ROOT/pages/security/authorization.adoc @@ -35,11 +35,11 @@ type User @node { } type Post @node @authorization(filter: [ - { where: { node: { author: { id_EQ: "$jwt.sub" } } } } + { where: { node: { authors: { some: { id: { eq: "$jwt.sub" } } } } } } ]) { title: String! content: String! - author: User! @relationship(type: "AUTHORED", direction: IN) + authors: [User!]! @relationship(type: "AUTHORED", direction: IN) } ---- @@ -59,11 +59,11 @@ For instance, to only require filtering for the reading and aggregating posts: [source, graphql, indent=0] ---- type Post @node @authorization(filter: [ - { operations: [READ, AGGREGATE], where: { node: { author: { id_EQ: "$jwt.sub" } } } } + { operations: [READ, AGGREGATE], where: { node: { author: { id: { eq: "$jwt.sub" } } } } } ]) { title: String! content: String! - author: User! @relationship(type: "AUTHORED", direction: IN) + authors: [User!]! @relationship(type: "AUTHORED", direction: IN) } ---- @@ -88,8 +88,8 @@ type JWT @jwt { } type User @node @authorization(validate: [ - { where: { node: { id_EQ: "$jwt.sub" } } } - { where: { jwt: { roles_INCLUDES: "admin" } } } + { where: { node: { id: { eq: "$jwt.sub" } } } } + { where: { jwt: { roles: { includes: "admin" } } } } ]) { id: ID! } @@ -112,11 +112,14 @@ For instance, to only require validation for the update or deletion of a post: [source, graphql, indent=0] ---- type Post @node @authorization(validate: [ - { operations: [UPDATE, DELETE], where: { node: { author: { id_EQ: "$jwt.sub" } } } } + { + operations: [UPDATE, DELETE] + where: { node: { authors: { some: { id: { eq: "$jwt.sub" } } } } } + } ]) { title: String! content: String! - author: User! @relationship(type: "AUTHORED", direction: IN) + authors: [User!]! @relationship(type: "AUTHORED", direction: IN) } ---- @@ -232,13 +235,16 @@ type User @node { } type Post @node @authorization(filter: [ - { where: { node: { author: { id_EQ: "$jwt.sub" } } } } - { requireAuthentication: false, operations: [READ], where: { node: { public_EQ: true } } } + { + requireAuthentication: false, + operations: [READ], + where: { node: { public: { eq: true } } } + } ]) { title: String! content: String! public: Boolean! - author: User! @relationship(type: "AUTHORED", direction: IN) + authors: [User!]! @relationship(type: "AUTHORED", direction: IN) } ---- @@ -252,7 +258,7 @@ For example, the following would allow for the update of a `User` node if the JW [source, graphql, indent=0] ---- type User @node @authorization(validate: [ - { operations: [UPDATE], where: { jwt: { roles_INCLUDES: "admin" } } } + { operations: [UPDATE], where: { jwt: { roles: { includes: "admin" } } } } { operations: [UPDATE], where: { node: { locked: false } } } ]) { id: ID! @@ -265,7 +271,7 @@ If you want to combine the rule that a user must be an admin with the rule that [source, graphql, indent=0] ---- type User @node @authorization(validate: [ - { operations: [UPDATE], where: { AND: [{ jwt: { roles_INCLUDES: "admin" } }, { node: { locked: false } }] } } + { operations: [UPDATE], where: { AND: [{ jwt: { roles: { includes: "admin" } } }, { node: { locked: false } }] } } ]) { id: ID! locked: Boolean! diff --git a/modules/ROOT/pages/security/operations.adoc b/modules/ROOT/pages/security/operations.adoc index 8f7e805c..e6ca4c33 100644 --- a/modules/ROOT/pages/security/operations.adoc +++ b/modules/ROOT/pages/security/operations.adoc @@ -125,7 +125,7 @@ For single `delete` mutations, rules with `DELETE` on the object are evaluated: [source, graphql, indent=0] ---- mutation { - deleteMovies(where: { title_EQ: "The Matrix" }) { # DELETE ON OBJECT Movie + deleteMovies(where: { title: { eq: "The Matrix" } }) { # DELETE ON OBJECT Movie nodesDeleted } } @@ -136,9 +136,9 @@ For `delete` mutations with nested delete operations, rules with operation `DELE [source, graphql, indent=0] ---- mutation { - deleteMovies( # DELETE ON OBJECT Movie - where: { title_EQ: "The Matrix" } - delete: { actors: { where: { node: { name_EQ: "Keanu" } } } } # DELETE ON OBJECT Actor + deleteMovies( # DELETE ON OBJECT Movie + where: { title: { eq: "The Matrix" } } + delete: { actors: { where: { node: { name: { eq: "Keanu" } } } } } # DELETE ON OBJECT Actor ) { nodesDeleted } @@ -153,16 +153,16 @@ For a complex `update` mutation with many effects, a variety of rules is evaluat ---- mutation { updateMovies( - where: { title_EQ: "The Matrix" } - connect: { actors: { where: { node: { name_EQ: "Keanu" } } } } # CONNECT ON OBJECT Actor and Movie - update: { # UPDATE ON OBJECT Movie - title: "Speed" # UPDATE ON FIELD_DEFINITION Movie.title + where: { title: { eq: "The Matrix" } } + connect: { actors: { where: { node: { name: { eq: "Keanu" } } } } } # CONNECT ON OBJECT Actor and Movie + update: { # UPDATE ON OBJECT Movie + title: { set: "Speed" } # UPDATE ON FIELD_DEFINITION Movie.title } ) { - movies { # READ ON OBJECT Movie - title # READ ON FIELD_DEFINITION Movie.title - actors { # READ ON OBJECT Actor - name # READ ON FIELD_DEFINITION Actor.name + movies { # READ ON OBJECT Movie + title # READ ON FIELD_DEFINITION Movie.title + actors { # READ ON OBJECT Actor + name # READ ON FIELD_DEFINITION Actor.name } } } diff --git a/modules/ROOT/pages/security/subscriptions-authorization.adoc b/modules/ROOT/pages/security/subscriptions-authorization.adoc index 0af2565c..536de37b 100644 --- a/modules/ROOT/pages/security/subscriptions-authorization.adoc +++ b/modules/ROOT/pages/security/subscriptions-authorization.adoc @@ -18,7 +18,7 @@ For instance, here is how to filter out `User` events which don't match the JWT [source, graphql, indent=0] ---- type User @node @subscriptionsAuthorization(filter: [ - { where: { node: { id_EQ: "$jwt.sub" } } } + { where: { node: { id: { eq: "$jwt.sub" } } } } ]) { id: ID! } @@ -37,7 +37,7 @@ For instance, to only require filtering for mutations to a type itself and not i [source, graphql, indent=0] ---- type User @node @subscriptionsAuthorization(filter: [ - { events: [CREATED, UPDATED, DELETED], where: { node: { id_EQ: "$jwt.sub" } } } + { events: [CREATED, UPDATED, DELETED], where: { node: { id: { eq: "$jwt.sub" } } } } ]) { id: ID! } @@ -53,7 +53,7 @@ For instance, in the case where some `Post` nodes are private whilst other `Post [source, graphql, indent=0] ---- type Post @node @subscriptionsAuthorization(filter: [ - { requireAuthentication: false, where: { node: { public_EQ: true } } } + { requireAuthentication: false, where: { node: { public: { eq: true } } } } ]) { title: String! content: String! diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 2e1ec440..1b084ef7 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -27,6 +27,7 @@ type Movie @node { title: String genre: String } +extend schema @subscription ---- A subscription to any newly created node of the `Movie` type should look like this: @@ -66,6 +67,7 @@ type Movie @node { title: String genre: String } +extend schema @subscription ---- A subscription to any node of the `Movie` type with its properties recently updated should look like this: @@ -105,6 +107,7 @@ type Movie @node { title: String genre: String } +extend schema @subscription ---- A subscription to any deleted nodes of the `Movie` type should look like this: diff --git a/modules/ROOT/pages/subscriptions/filtering.adoc b/modules/ROOT/pages/subscriptions/filtering.adoc index 610ec5f8..2772e9b5 100644 --- a/modules/ROOT/pages/subscriptions/filtering.adoc +++ b/modules/ROOT/pages/subscriptions/filtering.adoc @@ -26,10 +26,10 @@ For the `Boolean` type, these are the only available comparison operators. The following comparison operators are available for numeric types (`Int`, `Float`, xref::types/index.adoc[`BigInt`]): -* `_LT` -* `_LTE` -* `_GTE` -* `_GT` +* `lt` +* `lte` +* `gte` +* `gt` [NOTE] ==== @@ -40,19 +40,19 @@ Filtering on xref::types/temporal.adoc[Temporal types] and xref::types/spatial.a The following case-sensitive comparison operators are only available for use on `String` and `ID` types: -* `_STARTS_WITH` -* `_ENDS_WITH` -* `_CONTAINS` +* `startsWith` +* `endsWith` +* `contains` === Array comparison The following operator is available on non-array fields, and accepts an array argument: -* `_IN` +* `in` Conversely, the following operator is available on array fields, and accepts a single argument: -* `_INCLUDES` +* `includes` These operators are available for all types apart from `Boolean`. @@ -79,6 +79,7 @@ type Movie @node { averageRating: Float releasedIn: Int } +extend schema @subscription ---- Now, here is how filtering can be applied when creating a subscription: @@ -90,7 +91,7 @@ To filter subscriptions to `create` operations for movies by their genre, here i [source, graphql, indent=0] ---- subscription { - movieCreated(where: { genre_EQ: "Drama" }) { + movieCreated(where: { genre: { eq: "Drama" } }) { createdMovie { title } @@ -112,7 +113,7 @@ Here is how to filter subscription to `update` operations in movies with average [source, graphql, indent=0] ---- subscription { - movieUpdate(where: { averageRating_GT: 8 }) { + movieUpdate(where: { averageRating: { gt: 8 } }) { updatedMovie { title } @@ -144,14 +145,14 @@ mutation { mutation { updateTheMatrix: updateMovie( - where: { title_EQ: "The Matrix" } - update: { averageRating: 7.9 } + where: { title: { eq: "The Matrix" } } + update: { averageRating: { set: 7.9 }} ) { title }, updateResurrections: updateMovie( - where: { title_EQ: "The Matrix Resurrections" } - update: { averageRating: 8.9 } + where: { title: { eq: "The Matrix Resurrections" }} + update: { averageRating: { set: 8.9 }} ) { title } @@ -167,7 +168,7 @@ Here is how to filter subscription to `delete` operations in movies by their gen [source, graphql, indent=0] ---- subscription { - movieDeleted(where: { NOT: { genre_EQ: "Comedy" } }) { + movieDeleted(where: { NOT: { genre: { eq: "Comedy" } } }) { deletedMovie { title } @@ -196,12 +197,12 @@ They could subscribe to any updates that cover this particular set of interests subscription { movieUpdated(where: { OR: [ - { title_CONTAINS: "Matrix" }, - { genre_EQ: "comedy" }, + { title: { contains: "Matrix" } }, + { genre: { eq: "comedy"} }, { AND: [ - { NOT: { genre_EQ: "romantic comedy" } }, - { releasedIn_GT: 2000 }, - { releasedIn_LTE: 2005 } + { NOT: { genre: { eq: "romantic comedy" }} }, + { releasedIn: { gt: 2000 } }, + { releasedIn: { lte: 2005 } } ] }, ] }) { diff --git a/modules/ROOT/pages/subscriptions/getting-started.adoc b/modules/ROOT/pages/subscriptions/getting-started.adoc index 384565dc..613d04b9 100644 --- a/modules/ROOT/pages/subscriptions/getting-started.adoc +++ b/modules/ROOT/pages/subscriptions/getting-started.adoc @@ -64,6 +64,7 @@ const typeDefs = ` type Actor @node { name: String } + extend schema @subscription `; const driver = neo4j.driver("bolt://localhost:7687", neo4j.auth.basic("username", "password")); @@ -144,7 +145,7 @@ You can subscribe to new movies created with the following statement: [source, graphql, indent=0] ---- subscription { - movieCreated(where: { title_EQ: "The Matrix" }) { + movieCreated(where: { title: { eq: "The Matrix" } }) { createdMovie { title } @@ -158,7 +159,7 @@ You can try this with the following query: [source, graphql, indent=0] ---- mutation { - createMovies(input: [{ title_EQ: "The Matrix" }]) { + createMovies(input: [{ title: { eq: "The Matrix" } }]) { movies { title } diff --git a/modules/ROOT/pages/troubleshooting.adoc b/modules/ROOT/pages/troubleshooting.adoc index 28c84708..174d43ce 100644 --- a/modules/ROOT/pages/troubleshooting.adoc +++ b/modules/ROOT/pages/troubleshooting.adoc @@ -197,7 +197,7 @@ Then delete the director node: [source, graphql, indent=0] ---- mutation { - deletePeople(where: { name_EQ: "Robert Zemeckis" }) { + deletePeople(where: { name: { eq: "Robert Zemeckis" } }) { nodesDeleted } } diff --git a/modules/ROOT/pages/types/interfaces.adoc b/modules/ROOT/pages/types/interfaces.adoc index 8e6dcbc7..84ea2314 100644 --- a/modules/ROOT/pages/types/interfaces.adoc +++ b/modules/ROOT/pages/types/interfaces.adoc @@ -31,7 +31,7 @@ type Series implements Production @node { } type ActedIn @relationshipProperties { - role: String! + role: String } type Actor @node { @@ -52,7 +52,7 @@ The following will return all productions with title starting "The " for every a query GetProductionsStartingWithThe { actors { name - actedIn(where: { node: { title_STARTS_WITH: "The " } }) { + actedIn(where: { node: { title: { startsWith: "The " } } }) { title ... on Movie { runtime @@ -65,14 +65,14 @@ query GetProductionsStartingWithThe { } ---- -The following query will only return the movies with title starting with "The " for each actor by filtering them by `typename_IN`: +The following query will only return the movies with title starting with "The " for each actor by filtering them by `typename`: [source, graphql, indent=0] ---- query GetMoviesStartingWithThe { actors { name - actedIn(where: { node: { title_STARTS_WITH: "The ", typename_IN: [Movie] } }) { + actedIn(where: { node: { title: { startsWith: "The " }, typename: [Movie] } }) { title ... on Movie { runtime @@ -151,22 +151,26 @@ Take the following example: [source, graphql, indent=0] ---- mutation CreateActorAndProductions { - updateActors( - where: { name_EQ: "Woody Harrelson" } + updateActors( + where: { name: { eq: "Woody Harrelson" } } + update: { + actedIn: { connect: { - actedIn: { - where: { node: { title_EQ: "Zombieland" } } - connect: { actors: { where: { node: { name_EQ: "Emma Stone" } } } } - } - } - ) { - actors { - name - actedIn { - title - } + where: { node: { title: { eq: "Zombieland" } } } + connect: { + actors: { where: { node: { name: { eq: "Emma Stone" } } } } + } } + } + } + ) { + actors { + name + actedIn { + title + } } + } } ---- @@ -185,17 +189,17 @@ For example, the following query returns all productions (`movies` and `series`) [source, graphql, indent=0] ---- query GetProductionsStartingWithThe { - actors { - name - actedIn(where: { node: { title_STARTS_WITH: "The " } }) { - title - ... on Movie { - runtime - } - ... on Series { - episodes - } - } + actors { + name + actedIn(where: { title: { startsWith: "The " } }) { + title + ... on Movie { + runtime + } + ... on Series { + episodes + } } + } } ---- \ No newline at end of file diff --git a/modules/ROOT/pages/types/relationships.adoc b/modules/ROOT/pages/types/relationships.adoc index fc9a2773..80b1da47 100644 --- a/modules/ROOT/pages/types/relationships.adoc +++ b/modules/ROOT/pages/types/relationships.adoc @@ -45,19 +45,24 @@ type Movie @node { title: String! released: Int! actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN) - director: Person! @relationship(type: "DIRECTED", direction: IN) + directors: [Person!]! @relationship(type: "DIRECTED", direction: IN) } ---- Note that, in this query: * A `Person` can _act in_ or _direct_ multiple movies, and a `Movie` can have multiple actors. -However, it is rare for a `Movie` to have more than one director, so you can model this cardinality in your type definitions, to ensure accuracy of your data. -* A `Movie` isn't really a `Movie` without a director, and this has been signified by marking the `director` field as non-nullable. -This means that a `Movie` *must* have a `DIRECTED` relationship coming into it to be valid. + While it is rare for a `Movie` to have more than one director, the Neo4j GraphQL Library requires all relationships to be modeled as "many" relationships (using lists). +* Even conceptually one-to-one relationships, such as a movie having a single director, must be represented as arrays (e.g., `directors: [Person!]!`) because Neo4j cannot reliably enforce one-to-one cardinality between nodes. * To figure out whether the `direction` argument of the `@relationship` directive should be `IN` or `OUT`, visualize your relationships like in the diagram above, then model out the direction of the arrows. * The `@relationship` directive is a reference to Neo4j relationships, whereas in the schema, the phrase `edge(s)` is used to be consistent with the general API language used by Relay. +=== Neo4j GraphQL cardinality limitation + +It's important to understand that Neo4j cannot reliably enforce one-to-one cardinality between nodes in a graph database. +Therefore, all relationships in Neo4j GraphQL are modeled as "many" relationships (using lists) even if they represent one-to-one relationships conceptually. +As an alternative you can define your relationship with the `@cypher` directive, however some of the functionality provided by the Neo4j GraphQL Library will not be available. + === Relationship properties You can add relationship properties to the example in two steps: @@ -83,7 +88,7 @@ type Movie @node { title: String! released: Int! actors: [Person!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) - director: Person! @relationship(type: "DIRECTED", direction: IN) + directors: [Person!]! @relationship(type: "DIRECTED", direction: IN) } type ActedIn @relationshipProperties { @@ -142,7 +147,6 @@ type Person @node { ---- `queryDirection` can have the following values: - * `DIRECTED`: only directed queries can be performed on this relationship. * `UNDIRECTED`: only undirected queries can be performed on this relationship. @@ -160,20 +164,20 @@ mutation CreateMovieAndDirector { { title: "Forrest Gump" released: 1994 - director: { - create: { + directors: { + create: [{ node: { name: "Robert Zemeckis" born: 1951 } - } + }] } } ]) { movies { title released - director { + directors { name born } @@ -187,40 +191,40 @@ You then need to create the actor in this example, and connect them to the new ` [source, graphql, indent=0] ---- mutation CreateActor { - createPeople(input: [ - { - name: "Tom Hanks" - born: 1956 - actedInMovies: { - connect: { - where: { - node: { title_EQ: "Forrest Gump" } - } - edge: { - roles: ["Forrest"] - } - } - } + createPeople( + input: [ + { + name: "Tom Hanks" + born: 1956 + actedInMovies: { + connect: { + where: { node: { title: { eq: "Forrest Gump" } } } + edge: { roles: ["Forrest"] } + } } - ]) { - movies { - title - released - director { - name - born + } + ] + ) { + people { + name + born + actedInMovies { + title + released + actorsConnection { + edges { + properties { + roles } - actorsConnection { - edges { - roles - node { - name - born - } - } + node { + name + born } + } } + } } + } } ---- @@ -236,7 +240,7 @@ mutation CreateMovieDirectorAndActor { { title: "Forrest Gump" released: 1994 - director: { + directors: { create: { node: { name: "Robert Zemeckis" @@ -262,13 +266,15 @@ mutation CreateMovieDirectorAndActor { movies { title released - director { + directors { name born } actorsConnection { edges { - roles + properties { + roles + } node { name born @@ -289,16 +295,18 @@ Now that you have the `Movie` information in your database, you can query everyt [source, graphql, indent=0] ---- query { - movies(where: { title_EQ: "Forrest Gump" }) { + movies(where: { title: { eq: "Forrest Gump" } }) { title released - director { + directors { name born } actorsConnection { edges { - roles + properties { + roles + } node { name born diff --git a/modules/ROOT/pages/types/scalar.adoc b/modules/ROOT/pages/types/scalar.adoc index a5fe1bd6..3012893b 100644 --- a/modules/ROOT/pages/types/scalar.adoc +++ b/modules/ROOT/pages/types/scalar.adoc @@ -38,7 +38,7 @@ a| [source, graphql, indent=0] ---- query { - files(where: { size_EQ: 9223372036854775807 }) { + files(where: { size: { eq: 9223372036854775807 }}) { size } } diff --git a/modules/ROOT/pages/types/spatial.adoc b/modules/ROOT/pages/types/spatial.adoc index 62e65899..9a2577a6 100644 --- a/modules/ROOT/pages/types/spatial.adoc +++ b/modules/ROOT/pages/types/spatial.adoc @@ -18,7 +18,7 @@ In order to use it in your schema, add a field with a type `Point` to any other [source, graphql, indent=0] ---- -type TypeWithPoint @node { + type TypeWithPoint @node { location: Point! } ---- @@ -31,17 +31,19 @@ See xref::filtering.adoc#_filtering_spatial_types[Filtering spatial types] for f [source, graphql, indent=0] ---- -type Point @node { - latitude: Float! - longitude: Float! - height: Float +type Point { + longitude: Float! + latitude: Float! + height: Float + crs: String! + srid: Int! } ---- ==== Queries and mutations Due to the fact that `Point` is an object type, it has an additional type for input in queries and mutations. -However, this input type has the same shape as the object type: +However, these input types have the same shape as the object type: [source, graphql, indent=0] ---- @@ -50,6 +52,21 @@ input PointInput { longitude: Float! height: Float } + +input PointFilters { + eq: PointInput + in: [PointInput!] + distance: PointDistanceFilters +} + +input PointDistanceFilters { + from: PointInput! + gt: Float + gte: Float + lt: Float + lte: Float + eq: Float +} ---- For example, you can query for a `User` with an exact location: @@ -57,7 +74,7 @@ For example, you can query for a `User` with an exact location: [source, graphql, indent=0] ---- query Users($longitude: Float!, $latitude: Float!) { - users(where: { location_EQ: { longitude: $longitude, latitude: $latitude } }) { + users(where: { location: { eq: { longitude: $longitude, latitude: $latitude } } }) { name location { longitude @@ -106,10 +123,12 @@ See xref::filtering.adoc#_filtering_spatial_types[Filtering spatial types] for f [source, graphql, indent=0] ---- -type CartesianPoint @node { - x: Float! - y: Float! - z: Float +type CartesianPoint { + x: Float! + y: Float! + z: Float + crs: String! + srid: Int! } ---- @@ -121,8 +140,57 @@ However, this input type has the same shape as the object type: [source, graphql, indent=0] ---- input CartesianPointInput { - x: Float! - y: Float! - z: Float + x: Float! + y: Float! + z: Float +} + +input CartesianPointFilters { + eq: CartesianPointInput + in: [CartesianPointInput!] + distance: CartesianDistancePointFilters +} + +input CartesianDistancePointFilters { + from: CartesianPointInput! + gt: Float + gte: Float + lt: Float + lte: Float +} +---- + + +For example, you can query for a `User` with an exact location: + +[source, graphql, indent=0] +---- +query Users($x: Float!, $y: Float!, $z: Float!) { + users(where: { location: { eq: { x: $x, y: $y, z: $z } } }) { + name + location { + x + y + z + } + } +} +---- + +Or you can create a `User` with a location as follows: + +[source, graphql, indent=0] +---- +mutation CreateUsers($name: String!, $x: Float!, $y: Float!, $z: Float!) { + createUsers(input: [{ name: $name, location: { x: $x, y: $y, z: $z } }]) { + users { + name + location { + x + y + z + } + } + } } ---- diff --git a/modules/ROOT/pages/types/unions.adoc b/modules/ROOT/pages/types/unions.adoc index 6b726229..4a3a92ab 100644 --- a/modules/ROOT/pages/types/unions.adoc +++ b/modules/ROOT/pages/types/unions.adoc @@ -96,7 +96,7 @@ While this particular query only returns blogs, you could for instance use a fil query GetUsersWithAllContent { users { name - content(where: { Blog: { NOT: { title_EQ: null } }}) { + content(where: { Blog: { NOT: { title: { eq: null } } }}) { ... on Blog { title }