Skip to content

Commit ab0f96e

Browse files
committed
Utilize graphql framework to handle fragments correctly
With this change the way we handle projections is changed. We now rely on the graphql framework to parse fragments an arguments. This will simplify handling nested result fields like they are used in the javascript version of this library.
1 parent 7e45a7a commit ab0f96e

33 files changed

+358
-378
lines changed

core/src/main/kotlin/org/neo4j/graphql/DynamicProperties.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ object DynamicProperties {
4848
is BooleanValue -> input.isValue
4949
is EnumValue -> input.name
5050
is VariableReference -> variables[input.name]
51-
is ArrayValue -> input.values.map { v -> parse(v, variables) }
51+
is ArrayValue -> input.values.map { v -> parseNested(v, variables) }
5252
is ObjectValue -> throw IllegalArgumentException("deep structures not supported for dynamic properties")
5353
else -> Assert.assertShouldNeverHappen("We have covered all Value types")
5454
}

core/src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ import graphql.schema.GraphQLOutputType
66
import org.neo4j.cypherdsl.core.*
77
import java.util.*
88

9-
fun <T> Iterable<T>.joinNonEmpty(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
10-
return if (iterator().hasNext()) joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() else ""
11-
}
12-
139
fun queryParameter(value: Any?, vararg parts: String?): Parameter<*> {
1410
val name = when (value) {
1511
is VariableReference -> value.name
@@ -22,7 +18,6 @@ fun Expression.collect(type: GraphQLOutputType) = if (type.isList()) Functions.c
2218
fun StatementBuilder.OngoingReading.withSubQueries(subQueries: List<Statement>) = subQueries.fold(this, { it, sub -> it.call(sub) })
2319

2420
fun normalizeName(vararg parts: String?) = parts.mapNotNull { it?.capitalize() }.filter { it.isNotBlank() }.joinToString("").decapitalize()
25-
//fun normalizeName(vararg parts: String?) = parts.filterNot { it.isNullOrBlank() }.joinToString("_")
2621

2722
fun PropertyContainer.id(): FunctionInvocation = when (this) {
2823
is Node -> Functions.id(this)
@@ -35,3 +30,7 @@ fun String.toCamelCase(): String = Regex("[\\W_]([a-z])").replace(this) { it.gro
3530
fun <T> Optional<T>.unwrap(): T? = orElse(null)
3631

3732
fun String.asDescription() = Description(this, null, this.contains("\n"))
33+
34+
fun String.capitalize(): String = replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
35+
fun String.decapitalize(): String = replaceFirstChar { it.lowercase(Locale.getDefault()) }
36+
fun String.toUpperCase(): String = uppercase(Locale.getDefault())

core/src/main/kotlin/org/neo4j/graphql/GraphQLExtensions.kt

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_NAME
1717
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_TO
1818
import org.neo4j.graphql.handler.projection.ProjectionBase
1919
import org.slf4j.LoggerFactory
20-
import java.math.BigDecimal
21-
import java.math.BigInteger
2220

2321
fun Type<*>.name(): String? = if (this.inner() is TypeName) (this.inner() as TypeName).name else null
2422
fun Type<*>.inner(): Type<*> = when (this) {
@@ -64,7 +62,7 @@ fun GraphQLFieldsContainer.relationshipFor(name: String): RelationshipInfo<Graph
6462
(this as? GraphQLDirectiveContainer)
6563
?.getDirective(DirectiveConstants.RELATION)?.let {
6664
// do inverse mapping, if the current type is the `to` mapping of the relation
67-
it to (fieldObjectType.getRelevantFieldDefinition(it.getArgument(RELATION_TO, null))?.name == typeName)
65+
it to (fieldObjectType.getRelevantFieldDefinition(it.getArgument(RELATION_TO, null as String?))?.name == typeName)
6866
}
6967
?: throw IllegalStateException("Type ${this.name} needs an @relation directive")
7068
} else {
@@ -111,15 +109,16 @@ fun GraphQLType.ref(): GraphQLType = when (this) {
111109
}
112110

113111
fun Field.aliasOrName(): String = (this.alias ?: this.name)
114-
fun Field.contextualize(variable: String) = variable + this.aliasOrName().capitalize()
115-
fun Field.contextualize(variable: SymbolicName) = variable.value + this.aliasOrName().capitalize()
112+
fun SelectedField.aliasOrName(): String = (this.alias ?: this.name)
113+
fun SelectedField.contextualize(variable: String) = variable + this.aliasOrName().capitalize()
114+
fun SelectedField.contextualize(variable: SymbolicName) = variable.value + this.aliasOrName().capitalize()
116115

117116
fun GraphQLType.innerName(): String = inner().name()
118117
?: throw IllegalStateException("inner name cannot be retrieved for " + this.javaClass)
119118

120119
fun GraphQLFieldDefinition.propertyName() = getDirectiveArgument(PROPERTY, PROPERTY_NAME, this.name)!!
121120

122-
fun GraphQLFieldDefinition.dynamicPrefix(): String? = getDirectiveArgument(DYNAMIC, DYNAMIC_PREFIX, null)
121+
fun GraphQLFieldDefinition.dynamicPrefix(): String? = getDirectiveArgument(DYNAMIC, DYNAMIC_PREFIX, null as String?)
123122
fun GraphQLType.getInnerFieldsContainer() = inner() as? GraphQLFieldsContainer
124123
?: throw IllegalArgumentException("${this.innerName()} is neither an object nor an interface")
125124

@@ -168,7 +167,7 @@ fun GraphQLFieldDefinition.cypherDirective(): CypherDirective? = getDirective(CY
168167
originalStatement, rewrittenStatement, this.name, this.definition?.sourceLocation)
169168
}
170169
CypherDirective(rewrittenStatement, it.getMandatoryArgument(CYPHER_PASS_THROUGH, false))
171-
}
170+
}
172171

173172
data class CypherDirective(val statement: String, val passThrough: Boolean)
174173

@@ -228,21 +227,6 @@ fun TypeDefinitionRegistry.mutationTypeName() = this.getOperationType("mutation"
228227
fun TypeDefinitionRegistry.subscriptionTypeName() = this.getOperationType("subscription") ?: "Subscription"
229228
fun TypeDefinitionRegistry.getOperationType(name: String) = this.schemaDefinition().unwrap()?.operationTypeDefinitions?.firstOrNull { it.name == name }?.typeName?.name
230229

231-
fun Any?.asGraphQLValue(): Value<*> = when (this) {
232-
null -> NullValue.newNullValue().build()
233-
is Value<*> -> this
234-
is Array<*> -> ArrayValue.newArrayValue().values(this.map { it.asGraphQLValue() }).build()
235-
is Iterable<*> -> ArrayValue.newArrayValue().values(this.map { it.asGraphQLValue() }).build()
236-
is Map<*, *> -> ObjectValue.newObjectValue().objectFields(this.map { entry -> ObjectField(entry.key as String, entry.value.asGraphQLValue()) }).build()
237-
is Enum<*> -> EnumValue.newEnumValue().name(this.name).build()
238-
is Int -> IntValue.newIntValue(BigInteger.valueOf(this.toLong())).build()
239-
is Long -> IntValue.newIntValue(BigInteger.valueOf(this)).build()
240-
is Number -> FloatValue.newFloatValue(BigDecimal.valueOf(this as Double)).build()
241-
is Boolean -> BooleanValue.newBooleanValue(this).build()
242-
is String -> StringValue.newStringValue(this).build()
243-
else -> throw IllegalStateException("Cannot convert ${this.javaClass.name} into an graphql type")
244-
}
245-
246230
fun DataFetchingEnvironment.typeAsContainer() = this.fieldDefinition.type.inner() as? GraphQLFieldsContainer
247231
?: throw IllegalStateException("expect type of field ${this.logField()} to be GraphQLFieldsContainer, but was ${this.fieldDefinition.type.name()}")
248232

core/src/main/kotlin/org/neo4j/graphql/Neo4jTypes.kt

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package org.neo4j.graphql
22

3-
import graphql.language.Field
4-
import graphql.language.ObjectField
5-
import graphql.language.ObjectValue
63
import graphql.schema.GraphQLFieldDefinition
4+
import graphql.schema.SelectedField
75
import org.neo4j.cypherdsl.core.*
86
import org.neo4j.cypherdsl.core.Cypher
97
import org.neo4j.graphql.handler.BaseDataFetcherForContainer
@@ -19,7 +17,7 @@ data class TypeDefinition(
1917
)
2018

2119
class Neo4jTemporalConverter(name: String) : Neo4jSimpleConverter(name) {
22-
override fun projectField(variable: SymbolicName, field: Field, name: String): Any {
20+
override fun projectField(variable: SymbolicName, field: SelectedField, name: String): Any {
2321
return Cypher.call("toString").withArgs(variable.property(field.name)).asFunction()
2422
}
2523

@@ -31,28 +29,27 @@ class Neo4jTemporalConverter(name: String) : Neo4jSimpleConverter(name) {
3129
class Neo4jTimeConverter(name: String) : Neo4jConverter(name) {
3230

3331
override fun createCondition(
34-
objectField: ObjectField,
32+
fieldName: String,
3533
field: GraphQLFieldDefinition,
3634
parameter: Parameter<*>,
3735
conditionCreator: (Expression, Expression) -> Condition,
3836
propertyContainer: PropertyContainer
39-
): Condition = if (objectField.name == NEO4j_FORMATTED_PROPERTY_KEY) {
37+
): Condition = if (fieldName == NEO4j_FORMATTED_PROPERTY_KEY) {
4038
val exp = toExpression(parameter)
4139
conditionCreator(propertyContainer.property(field.name), exp)
4240
} else {
43-
super.createCondition(objectField, field, parameter, conditionCreator, propertyContainer)
41+
super.createCondition(fieldName, field, parameter, conditionCreator, propertyContainer)
4442
}
4543

46-
override fun projectField(variable: SymbolicName, field: Field, name: String): Any = when (name) {
44+
override fun projectField(variable: SymbolicName, field: SelectedField, name: String): Any = when (name) {
4745
NEO4j_FORMATTED_PROPERTY_KEY -> Cypher.call("toString").withArgs(variable.property(field.name)).asFunction()
4846
else -> super.projectField(variable, field, name)
4947
}
5048

5149
override fun getMutationExpression(value: Any, field: GraphQLFieldDefinition): BaseDataFetcherForContainer.PropertyAccessor {
5250
val fieldName = field.name
53-
return (value as? ObjectValue)
54-
?.objectFields
55-
?.find { it.name == NEO4j_FORMATTED_PROPERTY_KEY }
51+
return (value as? Map<*, *>)
52+
?.get(NEO4j_FORMATTED_PROPERTY_KEY)
5653
?.let {
5754
BaseDataFetcherForContainer.PropertyAccessor(fieldName) { variable ->
5855
val param = queryParameter(value, variable, fieldName)
@@ -91,14 +88,14 @@ open class Neo4jSimpleConverter(val name: String) {
9188
): Condition = conditionCreator(property, parameter)
9289

9390
open fun createCondition(
94-
objectField: ObjectField,
91+
fieldName: String,
9592
field: GraphQLFieldDefinition,
9693
parameter: Parameter<*>,
9794
conditionCreator: (Expression, Expression) -> Condition,
9895
propertyContainer: PropertyContainer
99-
): Condition = createCondition(propertyContainer.property(field.name, objectField.name), parameter, conditionCreator)
96+
): Condition = createCondition(propertyContainer.property(field.name, fieldName), parameter, conditionCreator)
10097

101-
open fun projectField(variable: SymbolicName, field: Field, name: String): Any = variable.property(field.name, name)
98+
open fun projectField(variable: SymbolicName, field: SelectedField, name: String): Any = variable.property(field.name, name)
10299

103100
open fun getMutationExpression(value: Any, field: GraphQLFieldDefinition): BaseDataFetcherForContainer.PropertyAccessor {
104101
return BaseDataFetcherForContainer.PropertyAccessor(field.name)

core/src/main/kotlin/org/neo4j/graphql/NoOpCoercing.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package org.neo4j.graphql
33
import graphql.schema.Coercing
44

55
object NoOpCoercing : Coercing<Any, Any> {
6-
override fun parseLiteral(input: Any?) = input
6+
override fun parseLiteral(input: Any?) = input?.toJavaValue()
77

88
override fun serialize(dataFetcherResult: Any?) = dataFetcherResult
99

10-
override fun parseValue(input: Any?) = input
10+
override fun parseValue(input: Any) = input.toJavaValue()
1111
}

core/src/main/kotlin/org/neo4j/graphql/Predicates.kt

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,17 @@ enum class FieldOperator(
5151
queriedField: String,
5252
propertyContainer: PropertyContainer,
5353
field: GraphQLFieldDefinition?,
54-
value: Any,
54+
value: Any?,
5555
schemaConfig: SchemaConfig,
5656
suffix: String? = null
5757
): List<Condition> {
5858
if (schemaConfig.useTemporalScalars && field?.type?.isNeo4jTemporalType() == true) {
5959
val neo4jTypeConverter = getNeo4jTypeConverter(field)
6060
val parameter = queryParameter(value, variablePrefix, queriedField, null, suffix)
61-
.withValue(value.toJavaValue())
61+
.withValue(value)
6262
return listOf(neo4jTypeConverter.createCondition(propertyContainer.property(field.name), parameter, conditionCreator))
6363
}
64-
return if (field?.type?.isNeo4jType() == true && value is ObjectValue) {
64+
return if (field?.type?.isNeo4jType() == true && value is Map<*, *>) {
6565
resolveNeo4jTypeConditions(variablePrefix, queriedField, propertyContainer, field, value, suffix)
6666
} else if (field?.isNativeId() == true) {
6767
val id = propertyContainer.id()
@@ -79,28 +79,29 @@ enum class FieldOperator(
7979
}
8080
}
8181

82-
private fun resolveNeo4jTypeConditions(variablePrefix: String, queriedField: String, propertyContainer: PropertyContainer, field: GraphQLFieldDefinition, value: ObjectValue, suffix: String?): List<Condition> {
82+
private fun resolveNeo4jTypeConditions(variablePrefix: String, queriedField: String, propertyContainer: PropertyContainer, field: GraphQLFieldDefinition, values: Map<*, *>, suffix: String?): List<Condition> {
8383
val neo4jTypeConverter = getNeo4jTypeConverter(field)
8484
val conditions = mutableListOf<Condition>()
8585
if (distance) {
86-
val parameter = queryParameter(value, variablePrefix, queriedField, suffix)
86+
val parameter = queryParameter(values, variablePrefix, queriedField, suffix)
8787
conditions += (neo4jTypeConverter as Neo4jPointConverter).createDistanceCondition(
8888
propertyContainer.property(field.propertyName()),
8989
parameter,
9090
conditionCreator
9191
)
9292
} else {
93-
value.objectFields.forEachIndexed { index, objectField ->
94-
val parameter = queryParameter(value, variablePrefix, queriedField, if (value.objectFields.size > 1) "And${index + 1}" else null, suffix, objectField.name)
95-
.withValue(objectField.value.toJavaValue())
93+
values.entries.forEachIndexed { index, (key, value) ->
94+
val fieldName = key.toString()
95+
val parameter = queryParameter(value, variablePrefix, queriedField, if (values.size > 1) "And${index + 1}" else null, suffix, fieldName)
96+
.withValue(value)
9697

97-
conditions += neo4jTypeConverter.createCondition(objectField, field, parameter, conditionCreator, propertyContainer)
98+
conditions += neo4jTypeConverter.createCondition(fieldName, field, parameter, conditionCreator, propertyContainer)
9899
}
99100
}
100101
return conditions
101102
}
102103

103-
private fun resolveCondition(variablePrefix: String, queriedField: String, property: Property, value: Any, suffix: String?): List<Condition> {
104+
private fun resolveCondition(variablePrefix: String, queriedField: String, property: Property, value: Any?, suffix: String?): List<Condition> {
104105
val parameter = queryParameter(value, variablePrefix, queriedField, suffix)
105106
val condition = conditionCreator(property, parameter)
106107
return listOf(condition)
@@ -140,14 +141,14 @@ enum class RelationOperator(val suffix: String) {
140141

141142
fun fieldName(fieldName: String) = fieldName + suffix
142143

143-
fun harmonize(type: GraphQLFieldsContainer, field: GraphQLFieldDefinition, value: Value<*>, queryFieldName: String) = when (field.type.isList()) {
144+
fun harmonize(type: GraphQLFieldsContainer, field: GraphQLFieldDefinition, value: Any?, queryFieldName: String) = when (field.type.isList()) {
144145
true -> when (this) {
145146
NOT -> when (value) {
146-
is NullValue -> NOT
147+
null -> NOT
147148
else -> NONE
148149
}
149150
EQ_OR_NOT_EXISTS -> when (value) {
150-
is NullValue -> EQ_OR_NOT_EXISTS
151+
null -> EQ_OR_NOT_EXISTS
151152
else -> {
152153
LOGGER.debug("$queryFieldName on type ${type.name} was used for filtering, consider using ${field.name}${EVERY.suffix} instead")
153154
EVERY
@@ -169,11 +170,11 @@ enum class RelationOperator(val suffix: String) {
169170
NONE
170171
}
171172
NOT -> when (value) {
172-
is NullValue -> NOT
173+
null -> NOT
173174
else -> NONE
174175
}
175176
EQ_OR_NOT_EXISTS -> when (value) {
176-
is NullValue -> EQ_OR_NOT_EXISTS
177+
null -> EQ_OR_NOT_EXISTS
177178
else -> SOME
178179
}
179180
else -> this

core/src/main/kotlin/org/neo4j/graphql/QueryContext.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package org.neo4j.graphql
22

33
data class QueryContext @JvmOverloads constructor(
44
/**
5-
* if true the <code>__typename</code> will be always returned for interfaces, no matter if it was queried or not
5+
* if true the <code>__typename</code> will always be returned for interfaces, no matter if it was queried or not
66
*/
77
var queryTypeOfInterfaces: Boolean = false,
88

@@ -18,4 +18,4 @@ data class QueryContext @JvmOverloads constructor(
1818
*/
1919
FILTER_AS_MATCH
2020
}
21-
}
21+
}

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package org.neo4j.graphql.handler
22

33
import graphql.language.Argument
44
import graphql.language.ArrayValue
5-
import graphql.language.ObjectValue
65
import graphql.schema.GraphQLFieldDefinition
76
import graphql.schema.GraphQLFieldsContainer
87
import graphql.schema.GraphQLType
@@ -42,7 +41,7 @@ abstract class BaseDataFetcherForContainer(schemaConfig: SchemaConfig) : BaseDat
4241
}
4342

4443
private fun defaultCallback(field: GraphQLFieldDefinition) =
45-
{ value: Any ->
44+
{ value: Any? ->
4645
val propertyName = field.propertyName()
4746
listOf(PropertyAccessor(propertyName) { variable -> queryParameter(value, variable, field.name) })
4847
}
@@ -55,23 +54,24 @@ abstract class BaseDataFetcherForContainer(schemaConfig: SchemaConfig) : BaseDat
5554
private fun dynamicPrefixCallback(field: GraphQLFieldDefinition, dynamicPrefix: String) =
5655
{ value: Any ->
5756
// maps each property of the map to the node
58-
(value as? ObjectValue)?.objectFields?.map { argField ->
57+
(value as? Map<*, *>)?.map { (key, value) ->
5958
PropertyAccessor(
60-
"$dynamicPrefix${argField.name}"
61-
) { variable -> queryParameter(argField.value, variable, "${field.name}${argField.name.capitalize()}") }
59+
"$dynamicPrefix${key}"
60+
) { variable -> queryParameter(value, variable, "${field.name}${(key as String).capitalize()}") }
6261
}
6362
}
6463

6564

66-
protected fun properties(variable: String, arguments: List<Argument>): Array<Any> =
65+
protected fun properties(variable: String, arguments: Map<String, Any>): Array<Any> =
6766
preparePredicateArguments(arguments)
6867
.flatMap { listOf(it.propertyName, it.toExpression(variable)) }
6968
.toTypedArray()
7069

71-
private fun preparePredicateArguments(arguments: List<Argument>): List<PropertyAccessor> {
70+
private fun preparePredicateArguments(arguments: Map<String, Any>): List<PropertyAccessor> {
7271
val predicates = arguments
73-
.mapNotNull { argument ->
74-
propertyFields[argument.name]?.invoke(argument.value)?.let { argument.name to it }
72+
.entries
73+
.mapNotNull { (key, value) ->
74+
propertyFields[key]?.invoke(value)?.let { key to it }
7575
}
7676
.toMap()
7777

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ class CreateTypeHandler private constructor(schemaConfig: SchemaConfig) : BaseDa
8484
val additionalTypes = (type as? GraphQLObjectType)?.interfaces?.map { it.name } ?: emptyList()
8585
val node = org.neo4j.cypherdsl.core.Cypher.node(type.name, *additionalTypes.toTypedArray()).named(variable)
8686

87-
val properties = properties(variable, field.arguments)
88-
val (mapProjection, subQueries) = projectFields(node, field, type, env)
87+
val properties = properties(variable, env.arguments)
88+
val (mapProjection, subQueries) = projectFields(node, type, env)
8989

9090
return org.neo4j.cypherdsl.core.Cypher.create(node.withProperties(*properties))
9191
.with(node)

0 commit comments

Comments
 (0)