Skip to content

Use default values for sorting and paging on fields #176

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ class CypherDirectiveHandler(
} else {
query.returning(node.`as`(field.aliasOrName()))
}
val ordering = orderBy(node, field.arguments)
val skipLimit = SkipLimit(variable, field.arguments)
val ordering = orderBy(node, field.arguments, fieldDefinition)
val skipLimit = SkipLimit(variable, field.arguments, fieldDefinition)

val resultWithSkipLimit = readingWithWhere
.let { if (ordering != null) skipLimit.format(it.orderBy(*ordering.toTypedArray())) else skipLimit.format(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ class QueryHandler private constructor(
if (!schemaConfig.query.enabled || schemaConfig.query.exclude.contains(typeName)) {
return false
}
if (getRelevantFields(type).isEmpty()) {
if (getRelevantFields(type).isEmpty() && !hasRelationships(type)) {
return false
}
return true
}

private fun hasRelationships(type: GraphQLFieldsContainer): Boolean = type.fieldDefinitions.any { it.isRelationship() }

private fun getRelevantFields(type: GraphQLFieldsContainer): List<GraphQLFieldDefinition> {
return type
.relevantFields()
Expand All @@ -94,8 +96,8 @@ class QueryHandler private constructor(
match.where(where)
}

val ordering = orderBy(propertyContainer, field.arguments)
val skipLimit = SkipLimit(variable, field.arguments)
val ordering = orderBy(propertyContainer, field.arguments, fieldDefinition)
val skipLimit = SkipLimit(variable, field.arguments, fieldDefinition)

val projectionEntries = projectFields(propertyContainer, field, type, env)
val mapProjection = propertyContainer.project(projectionEntries).`as`(field.aliasOrName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ open class ProjectionBase {
const val TYPE_NAME = "__typename"
}

fun orderBy(node: PropertyContainer, args: MutableList<Argument>): List<SortItem>? {
val values = getOrderByArgs(args)
fun orderBy(node: PropertyContainer, args: MutableList<Argument>, fieldDefinition: GraphQLFieldDefinition?): List<SortItem>? {
val values = getOrderByArgs(args, fieldDefinition)
if (values.isEmpty()) {
return null
}
Expand All @@ -39,9 +39,10 @@ open class ProjectionBase {
}
}

private fun getOrderByArgs(args: MutableList<Argument>): List<Pair<String, Sort>> {
val arg = args.find { it.name == ORDER_BY }
return arg?.value
private fun getOrderByArgs(args: MutableList<Argument>, fieldDefinition: GraphQLFieldDefinition?): List<Pair<String, Sort>> {
val orderBy = args.find { it.name == ORDER_BY }?.value
?: fieldDefinition?.getArgument(ORDER_BY)?.defaultValue?.asGraphQLValue()
return orderBy
?.let { it ->
when (it) {
is ArrayValue -> it.values.map { it.toJavaValue().toString() }
Expand Down Expand Up @@ -82,7 +83,7 @@ open class ProjectionBase {
}
?.let { parseFilter(it as ObjectValue, type) }
?.let {
val filterCondition = handleQuery(normalizeName(FILTER ,variable), "", propertyContainer, it, type)
val filterCondition = handleQuery(normalizeName(FILTER, variable), "", propertyContainer, it, type)
result.and(filterCondition)
}
?: result
Expand Down Expand Up @@ -118,7 +119,7 @@ open class ProjectionBase {
RelationOperator.NONE -> Predicates.none(cond)
else -> null
}?.let {
val targetNode = predicate.relNode.named(normalizeName(variablePrefix,predicate.relationshipInfo.typeName))
val targetNode = predicate.relNode.named(normalizeName(variablePrefix, predicate.relationshipInfo.typeName))
val parsedQuery2 = parseFilter(objectField.value as ObjectValue, type)
val condition = handleQuery(targetNode.requiredSymbolicName.value, "", targetNode, parsedQuery2, type)
var where = it
Expand Down Expand Up @@ -324,7 +325,7 @@ open class ProjectionBase {
val fieldProjection = projectFields(anyNode(childVariable), childVariable, field, fieldObjectType, env, variableSuffix)

val comprehension = listWith(childVariable).`in`(expression).returning(childVariable.project(fieldProjection))
val skipLimit = SkipLimit(childVariableName, field.arguments)
val skipLimit = SkipLimit(childVariableName, field.arguments, fieldDefinition)
return skipLimit.slice(fieldType.isList(), comprehension)
}

Expand Down Expand Up @@ -372,8 +373,8 @@ open class ProjectionBase {
else -> node(nodeType.name).named(childVariableName) to null
}

val skipLimit = SkipLimit(childVariable, field.arguments)
val orderBy = getOrderByArgs(field.arguments)
val skipLimit = SkipLimit(childVariable, field.arguments, fieldDefinition)
val orderBy = getOrderByArgs(field.arguments, fieldDefinition)
val sortByNeo4jTypeFields = orderBy
.filter { (property, _) -> nodeType.getFieldDefinition(property)?.isNeo4jType() == true }
.map { (property, _) -> property }
Expand Down Expand Up @@ -410,8 +411,9 @@ open class ProjectionBase {

class SkipLimit(variable: String,
arguments: List<Argument>,
private val skip: Parameter<*>? = convertArgument(variable, arguments, OFFSET),
private val limit: Parameter<*>? = convertArgument(variable, arguments, FIRST)) {
fieldDefinition: GraphQLFieldDefinition?,
private val skip: Parameter<*>? = convertArgument(variable, arguments, fieldDefinition, OFFSET),
private val limit: Parameter<*>? = convertArgument(variable, arguments, fieldDefinition, FIRST)) {


fun <T> format(returning: T): StatementBuilder.BuildableStatement where T : TerminalExposesSkip, T : TerminalExposesLimit {
Expand All @@ -429,9 +431,12 @@ open class ProjectionBase {
}

companion object {
private fun convertArgument(variable: String, arguments: List<Argument>, name: String): Parameter<*>? {
val argument = arguments.find { it.name.toLowerCase() == name } ?: return null
return queryParameter(argument.value, variable, argument.name)
private fun convertArgument(variable: String, arguments: List<Argument>, fieldDefinition: GraphQLFieldDefinition?, name: String): Parameter<*>? {
val value = arguments
.find { it.name.toLowerCase() == name }?.value
?: fieldDefinition?.getArgument(name)?.defaultValue
?: return null
return queryParameter(value, variable, name)
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions core/src/test/resources/augmentation-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ type Publisher {
}

type Query {
hasMovies(filter: _HasMoviesFilter, first: Int, offset: Int): [HasMovies!]!
knows0(filter: _Knows0Filter, first: Int, id: ID, offset: Int, orderBy: [_Knows0Ordering!]): [Knows0!]!
knows1(filter: _Knows1Filter, first: Int, id: ID, offset: Int, orderBy: [_Knows1Ordering!]): [Knows1!]!
knows4(_id: ID, filter: _Knows4Filter, first: Int, offset: Int, orderBy: [_Knows4Ordering!]): [Knows4!]!
Expand Down Expand Up @@ -649,6 +650,26 @@ enum _PublisherOrdering {

scalar DynamicProperties

input _HasMoviesFilter {
AND: [_HasMoviesFilter!]
NOT: [_HasMoviesFilter!]
OR: [_HasMoviesFilter!]
"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"
movies: _MovieFilter
"Filters only those `HasMovies` for which all `movies`-relationships matches this filter"
movies_every: _MovieFilter
"Filters only those `HasMovies` for which none of the `movies`-relationships matches this filter"
movies_none: _MovieFilter
"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"
movies_not: _MovieFilter
"Filters only those `HasMovies` for which exactly one `movies`-relationship matches this filter"
movies_single: _MovieFilter
"Filters only those `HasMovies` for which at least one `movies`-relationship matches this filter"
movies_some: _MovieFilter
}

input _HasMoviesInput

input _Knows0Filter {
AND: [_Knows0Filter!]
NOT: [_Knows0Filter!]
Expand Down
141 changes: 141 additions & 0 deletions core/src/test/resources/issues/gh-169.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
:toc:

= Github Issue #169: Default values for sorting and paging on fields should be respected

== Schema

[source,graphql,schema=true]
----
type Movie {
actors(first: Int = 3, offset: Int = 0, name: String, orderBy: [_ActorOrdering!] = [name_desc]): [Actor] @relation(name: "ACTED_IN", direction:IN)
}
type Actor {
name: String
}

type Query {
test(first: Int = 3, offset: Int = 0, orderBy: [_ActorOrdering!] = [name_asc]): [Actor!]!
}

enum _ActorOrdering {
name_asc
name_desc
}
----

== Test default values on relation field

.GraphQL-Query
[source,graphql]
----
{
movie {
actors {
name
}
}
}
----

.Cypher Params
[source,json]
----
{
"movieActorsFirst" : 3,
"movieActorsOffset" : 0
}
----

== Test customized values on relation field

.GraphQL-Query
[source,graphql]
----
{
movie {
actors(first: 5, offset: 2, orderBy: [name_asc]) {
name
}
}
}
----

.Cypher Params
[source,json]
----
{
"movieActorsFirst" : 5,
"movieActorsOffset" : 2
}
----

.Cypher
[source,cypher]
----
MATCH (movie:Movie)
RETURN movie {
actors: apoc.coll.sortMulti([(movie)<-[:ACTED_IN]-(movieActors:Actor) | movieActors {
.name
}], ['^name'])[$movieActorsOffset..($movieActorsOffset + $movieActorsFirst)]
} AS movie
----

== Test default values on root field

.GraphQL-Query
[source,graphql]
----
{
test {
name
}
}
----

.Cypher Params
[source,json]
----
{
"testFirst" : 3,
"testOffset" : 0
}
----

.Cypher
[source,cypher]
----
MATCH (test:Actor)
RETURN test {
.name
} AS test ORDER BY test.name ASC SKIP $testOffset LIMIT $testFirst
----

== Test customized values on root field

.GraphQL-Query
[source,graphql]
----
{
test(first: 5, offset: 2, orderBy: [name_desc]) {
name
}
}
----

.Cypher Params
[source,json]
----
{
"testFirst" : 5,
"testOffset" : 2
}
----

.Cypher
[source,cypher]
----
MATCH (test:Actor)
RETURN test {
.name
} AS test ORDER BY test.name DESC SKIP $testOffset LIMIT $testFirst
----
Loading