Skip to content

Commit fb9ab3a

Browse files
authored
Use default values for sorting and paging on fields (#176)
resolves #169
1 parent 7e6804c commit fb9ab3a

File tree

6 files changed

+218
-37
lines changed

6 files changed

+218
-37
lines changed

core/src/main/kotlin/org/neo4j/graphql/handler/CypherDirectiveHandler.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ class CypherDirectiveHandler(
4747
} else {
4848
query.returning(node.`as`(field.aliasOrName()))
4949
}
50-
val ordering = orderBy(node, field.arguments)
51-
val skipLimit = SkipLimit(variable, field.arguments)
50+
val ordering = orderBy(node, field.arguments, fieldDefinition)
51+
val skipLimit = SkipLimit(variable, field.arguments, fieldDefinition)
5252

5353
val resultWithSkipLimit = readingWithWhere
5454
.let { if (ordering != null) skipLimit.format(it.orderBy(*ordering.toTypedArray())) else skipLimit.format(it) }

core/src/main/kotlin/org/neo4j/graphql/handler/QueryHandler.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,14 @@ class QueryHandler private constructor(
6262
if (!schemaConfig.query.enabled || schemaConfig.query.exclude.contains(typeName)) {
6363
return false
6464
}
65-
if (getRelevantFields(type).isEmpty()) {
65+
if (getRelevantFields(type).isEmpty() && !hasRelationships(type)) {
6666
return false
6767
}
6868
return true
6969
}
7070

71+
private fun hasRelationships(type: GraphQLFieldsContainer): Boolean = type.fieldDefinitions.any { it.isRelationship() }
72+
7173
private fun getRelevantFields(type: GraphQLFieldsContainer): List<GraphQLFieldDefinition> {
7274
return type
7375
.relevantFields()
@@ -94,8 +96,8 @@ class QueryHandler private constructor(
9496
match.where(where)
9597
}
9698

97-
val ordering = orderBy(propertyContainer, field.arguments)
98-
val skipLimit = SkipLimit(variable, field.arguments)
99+
val ordering = orderBy(propertyContainer, field.arguments, fieldDefinition)
100+
val skipLimit = SkipLimit(variable, field.arguments, fieldDefinition)
99101

100102
val projectionEntries = projectFields(propertyContainer, field, type, env)
101103
val mapProjection = propertyContainer.project(projectionEntries).`as`(field.aliasOrName())

core/src/main/kotlin/org/neo4j/graphql/handler/projection/ProjectionBase.kt

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ open class ProjectionBase {
2727
const val TYPE_NAME = "__typename"
2828
}
2929

30-
fun orderBy(node: PropertyContainer, args: MutableList<Argument>): List<SortItem>? {
31-
val values = getOrderByArgs(args)
30+
fun orderBy(node: PropertyContainer, args: MutableList<Argument>, fieldDefinition: GraphQLFieldDefinition?): List<SortItem>? {
31+
val values = getOrderByArgs(args, fieldDefinition)
3232
if (values.isEmpty()) {
3333
return null
3434
}
@@ -39,9 +39,10 @@ open class ProjectionBase {
3939
}
4040
}
4141

42-
private fun getOrderByArgs(args: MutableList<Argument>): List<Pair<String, Sort>> {
43-
val arg = args.find { it.name == ORDER_BY }
44-
return arg?.value
42+
private fun getOrderByArgs(args: MutableList<Argument>, fieldDefinition: GraphQLFieldDefinition?): List<Pair<String, Sort>> {
43+
val orderBy = args.find { it.name == ORDER_BY }?.value
44+
?: fieldDefinition?.getArgument(ORDER_BY)?.defaultValue?.asGraphQLValue()
45+
return orderBy
4546
?.let { it ->
4647
when (it) {
4748
is ArrayValue -> it.values.map { it.toJavaValue().toString() }
@@ -324,7 +325,7 @@ open class ProjectionBase {
324325
val fieldProjection = projectFields(anyNode(childVariable), childVariable, field, fieldObjectType, env, variableSuffix)
325326

326327
val comprehension = listWith(childVariable).`in`(expression).returning(childVariable.project(fieldProjection))
327-
val skipLimit = SkipLimit(childVariableName, field.arguments)
328+
val skipLimit = SkipLimit(childVariableName, field.arguments, fieldDefinition)
328329
return skipLimit.slice(fieldType.isList(), comprehension)
329330
}
330331

@@ -402,8 +403,8 @@ open class ProjectionBase {
402403
else -> node(nodeType.name).named(childVariableName) to null
403404
}
404405

405-
val skipLimit = SkipLimit(childVariable, field.arguments)
406-
val orderBy = getOrderByArgs(field.arguments)
406+
val skipLimit = SkipLimit(childVariable, field.arguments, fieldDefinition)
407+
val orderBy = getOrderByArgs(field.arguments, fieldDefinition)
407408
val sortByNeo4jTypeFields = orderBy
408409
.filter { (property, _) -> nodeType.getFieldDefinition(property)?.isNeo4jType() == true }
409410
.map { (property, _) -> property }
@@ -440,8 +441,9 @@ open class ProjectionBase {
440441

441442
class SkipLimit(variable: String,
442443
arguments: List<Argument>,
443-
private val skip: Parameter<*>? = convertArgument(variable, arguments, OFFSET),
444-
private val limit: Parameter<*>? = convertArgument(variable, arguments, FIRST)) {
444+
fieldDefinition: GraphQLFieldDefinition?,
445+
private val skip: Parameter<*>? = convertArgument(variable, arguments, fieldDefinition, OFFSET),
446+
private val limit: Parameter<*>? = convertArgument(variable, arguments, fieldDefinition, FIRST)) {
445447

446448

447449
fun <T> format(returning: T): StatementBuilder.BuildableStatement where T : TerminalExposesSkip, T : TerminalExposesLimit {
@@ -459,9 +461,12 @@ open class ProjectionBase {
459461
}
460462

461463
companion object {
462-
private fun convertArgument(variable: String, arguments: List<Argument>, name: String): Parameter<*>? {
463-
val argument = arguments.find { it.name.toLowerCase() == name } ?: return null
464-
return queryParameter(argument.value, variable, argument.name)
464+
private fun convertArgument(variable: String, arguments: List<Argument>, fieldDefinition: GraphQLFieldDefinition?, name: String): Parameter<*>? {
465+
val value = arguments
466+
.find { it.name.toLowerCase() == name }?.value
467+
?: fieldDefinition?.getArgument(name)?.defaultValue
468+
?: return null
469+
return queryParameter(value, variable, name)
465470
}
466471
}
467472
}

core/src/test/resources/augmentation-tests.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ type Publisher {
462462
}
463463
464464
type Query {
465+
hasMovies(filter: _HasMoviesFilter, first: Int, offset: Int): [HasMovies!]!
465466
knows0(filter: _Knows0Filter, first: Int, id: ID, offset: Int, orderBy: [_Knows0Ordering!]): [Knows0!]!
466467
knows1(filter: _Knows1Filter, first: Int, id: ID, offset: Int, orderBy: [_Knows1Ordering!]): [Knows1!]!
467468
knows4(_id: ID, filter: _Knows4Filter, first: Int, offset: Int, orderBy: [_Knows4Ordering!]): [Knows4!]!
@@ -649,6 +650,26 @@ enum _PublisherOrdering {
649650
650651
scalar DynamicProperties
651652
653+
input _HasMoviesFilter {
654+
AND: [_HasMoviesFilter!]
655+
NOT: [_HasMoviesFilter!]
656+
OR: [_HasMoviesFilter!]
657+
"Filters only those `HasMovies` for which all `movies`-relationship matches this filter. If `null` is passed to this field, only those `HasMovies` will be filtered which has no `movies`-relations"
658+
movies: _MovieFilter
659+
"Filters only those `HasMovies` for which all `movies`-relationships matches this filter"
660+
movies_every: _MovieFilter
661+
"Filters only those `HasMovies` for which none of the `movies`-relationships matches this filter"
662+
movies_none: _MovieFilter
663+
"Filters only those `HasMovies` for which all `movies`-relationship does not match this filter. If `null` is passed to this field, only those `HasMovies` will be filtered which has any `movies`-relation"
664+
movies_not: _MovieFilter
665+
"Filters only those `HasMovies` for which exactly one `movies`-relationship matches this filter"
666+
movies_single: _MovieFilter
667+
"Filters only those `HasMovies` for which at least one `movies`-relationship matches this filter"
668+
movies_some: _MovieFilter
669+
}
670+
671+
input _HasMoviesInput
672+
652673
input _Knows0Filter {
653674
AND: [_Knows0Filter!]
654675
NOT: [_Knows0Filter!]
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
:toc:
2+
3+
= Github Issue #169: Default values for sorting and paging on fields should be respected
4+
5+
== Schema
6+
7+
[source,graphql,schema=true]
8+
----
9+
type Movie {
10+
actors(first: Int = 3, offset: Int = 0, name: String, orderBy: [_ActorOrdering!] = [name_desc]): [Actor] @relation(name: "ACTED_IN", direction:IN)
11+
}
12+
type Actor {
13+
name: String
14+
}
15+
16+
type Query {
17+
test(first: Int = 3, offset: Int = 0, orderBy: [_ActorOrdering!] = [name_asc]): [Actor!]!
18+
}
19+
20+
enum _ActorOrdering {
21+
name_asc
22+
name_desc
23+
}
24+
----
25+
26+
== Test default values on relation field
27+
28+
.GraphQL-Query
29+
[source,graphql]
30+
----
31+
{
32+
movie {
33+
actors {
34+
name
35+
}
36+
}
37+
}
38+
----
39+
40+
.Cypher Params
41+
[source,json]
42+
----
43+
{
44+
"movieActorsFirst" : 3,
45+
"movieActorsOffset" : 0
46+
}
47+
----
48+
49+
== Test customized values on relation field
50+
51+
.GraphQL-Query
52+
[source,graphql]
53+
----
54+
{
55+
movie {
56+
actors(first: 5, offset: 2, orderBy: [name_asc]) {
57+
name
58+
}
59+
}
60+
}
61+
----
62+
63+
.Cypher Params
64+
[source,json]
65+
----
66+
{
67+
"movieActorsFirst" : 5,
68+
"movieActorsOffset" : 2
69+
}
70+
----
71+
72+
.Cypher
73+
[source,cypher]
74+
----
75+
MATCH (movie:Movie)
76+
RETURN movie {
77+
actors: apoc.coll.sortMulti([(movie)<-[:ACTED_IN]-(movieActors:Actor) | movieActors {
78+
.name
79+
}], ['^name'])[$movieActorsOffset..($movieActorsOffset + $movieActorsFirst)]
80+
} AS movie
81+
----
82+
83+
== Test default values on root field
84+
85+
.GraphQL-Query
86+
[source,graphql]
87+
----
88+
{
89+
test {
90+
name
91+
}
92+
}
93+
----
94+
95+
.Cypher Params
96+
[source,json]
97+
----
98+
{
99+
"testFirst" : 3,
100+
"testOffset" : 0
101+
}
102+
----
103+
104+
.Cypher
105+
[source,cypher]
106+
----
107+
MATCH (test:Actor)
108+
RETURN test {
109+
.name
110+
} AS test ORDER BY test.name ASC SKIP $testOffset LIMIT $testFirst
111+
----
112+
113+
== Test customized values on root field
114+
115+
.GraphQL-Query
116+
[source,graphql]
117+
----
118+
{
119+
test(first: 5, offset: 2, orderBy: [name_desc]) {
120+
name
121+
}
122+
}
123+
----
124+
125+
.Cypher Params
126+
[source,json]
127+
----
128+
{
129+
"testFirst" : 5,
130+
"testOffset" : 2
131+
}
132+
----
133+
134+
.Cypher
135+
[source,cypher]
136+
----
137+
MATCH (test:Actor)
138+
RETURN test {
139+
.name
140+
} AS test ORDER BY test.name DESC SKIP $testOffset LIMIT $testFirst
141+
----

0 commit comments

Comments
 (0)