Skip to content

Commit 848520b

Browse files
authored
Migrate to neo4j-cypher-dsl (#171)
* Migrate to neo4j-cypher-dsl This is a refactoring for using the neo4j-cypher-dsl everywhere in this project. This let us harmonizes the current logic with the one provided by the OptimizedFilterHandler.
1 parent e459380 commit 848520b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2491
-1920
lines changed

.run/Update All tests.run.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<component name="ProjectRunConfigurationManager">
2+
<configuration default="false" name="Update All tests" type="JUnit" factoryName="JUnit">
3+
<module name="neo4j-graphql-java" />
4+
<option name="PACKAGE_NAME" value="" />
5+
<option name="MAIN_CLASS_NAME" value="" />
6+
<option name="METHOD_NAME" value="" />
7+
<option name="TEST_OBJECT" value="uniqueId" />
8+
<option name="VM_PARAMETERS" value="-ea -Dneo4j-graphql-java.update-test-file=true" />
9+
<uniqueIds>
10+
<uniqueId value="[engine:junit-jupiter]/[class:org.neo4j.graphql.CypherTests]" />
11+
</uniqueIds>
12+
<method v="2">
13+
<option name="Make" enabled="true" />
14+
</method>
15+
</configuration>
16+
</component>

core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
<dependency>
8282
<groupId>org.neo4j</groupId>
8383
<artifactId>neo4j-cypher-dsl</artifactId>
84-
<version>2020.0.1</version>
84+
<version>2021.0.1</version>
8585
</dependency>
8686
<dependency>
8787
<groupId>ch.qos.logback</groupId>

core/src/main/kotlin/org/neo4j/cypherdsl/core/PassThrough.kt

Lines changed: 0 additions & 10 deletions
This file was deleted.

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package org.neo4j.graphql
22

33
class DirectiveConstants {
4+
45
companion object {
6+
57
const val RELATION = "relation"
68
const val RELATION_NAME = "name"
79
const val RELATION_DIRECTION = "direction"
8-
const val RELATION_DIRECTION_IN = "IN"
9-
const val RELATION_DIRECTION_OUT = "OUT"
10-
const val RELATION_DIRECTION_BOTH = "BOTH"
1110
const val RELATION_FROM = "from"
1211
const val RELATION_TO = "to"
1312

@@ -20,4 +19,4 @@ class DirectiveConstants {
2019
const val DYNAMIC = "dynamic"
2120
const val DYNAMIC_PREFIX = "prefix"
2221
}
23-
}
22+
}

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

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,18 @@ object DynamicProperties {
2929

3030
@Throws(CoercingParseLiteralException::class)
3131
private fun parse(input: Any, variables: Map<String, Any>): Any? {
32-
if (input !is Value<*>) {
33-
throw CoercingParseLiteralException("Expected AST type 'StringValue' but was '${input::class.java.simpleName}'.")
34-
} else if (input is NullValue) {
35-
return null
36-
} else if (input is FloatValue) {
37-
return input.value
38-
} else if (input is StringValue) {
39-
return input.value
40-
} else if (input is IntValue) {
41-
return input.value
42-
} else if (input is BooleanValue) {
43-
return input.isValue
44-
} else if (input is EnumValue) {
45-
return input.name
46-
} else if (input is VariableReference) {
47-
val varName = input.name
48-
return variables[varName]
49-
} else {
50-
val values: List<*>
51-
return when (input) {
52-
is ArrayValue -> {
53-
values = input.values
54-
values.map { v -> parse(v, variables) }
55-
}
56-
is ObjectValue -> {
57-
throw IllegalArgumentException("deep structures not supported for dynamic properties")
58-
}
59-
else -> Assert.assertShouldNeverHappen("We have covered all Value types")
60-
}
32+
return when (input) {
33+
!is Value<*> -> throw CoercingParseLiteralException("Expected AST type 'StringValue' but was '${input::class.java.simpleName}'.")
34+
is NullValue -> null
35+
is FloatValue -> input.value
36+
is StringValue -> input.value
37+
is IntValue -> input.value
38+
is BooleanValue -> input.isValue
39+
is EnumValue -> input.name
40+
is VariableReference -> variables[input.name]
41+
is ArrayValue -> input.values.map { v -> parse(v, variables) }
42+
is ObjectValue -> throw IllegalArgumentException("deep structures not supported for dynamic properties")
43+
else -> Assert.assertShouldNeverHappen("We have covered all Value types")
6144
}
6245
}
6346

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
package org.neo4j.graphql
22

3+
import graphql.language.VariableReference
34
import graphql.schema.GraphQLArgument
45
import graphql.schema.GraphQLInputType
56
import graphql.schema.GraphQLType
6-
import java.io.PrintWriter
7-
import java.io.StringWriter
8-
9-
fun Throwable.stackTraceAsString(): String {
10-
val sw = StringWriter()
11-
this.printStackTrace(PrintWriter(sw))
12-
return sw.toString()
13-
}
7+
import org.neo4j.cypherdsl.core.*
148

159
fun <T> Iterable<T>.joinNonEmpty(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
1610
return if (iterator().hasNext()) joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() else ""
@@ -23,3 +17,22 @@ fun input(name: String, type: GraphQLType): GraphQLArgument {
2317
.type((type.ref() as? GraphQLInputType)
2418
?: throw IllegalArgumentException("${type.innerName()} is not allowed for input")).build()
2519
}
20+
21+
fun queryParameter(value: Any?, vararg parts: String?): Parameter<Any> {
22+
val name = when (value) {
23+
is VariableReference -> value.name
24+
else -> normalizeName(*parts)
25+
}
26+
return org.neo4j.cypherdsl.core.Cypher.parameter(name).withValue(value?.toJavaValue())
27+
}
28+
29+
fun normalizeName(vararg parts: String?) = parts.mapNotNull { it?.capitalize() }.filter { it.isNotBlank() }.joinToString("").decapitalize()
30+
//fun normalizeName(vararg parts: String?) = parts.filterNot { it.isNullOrBlank() }.joinToString("_")
31+
32+
fun PropertyContainer.id(): FunctionInvocation = when (this) {
33+
is Node -> Functions.id(this)
34+
is Relationship -> Functions.id(this)
35+
else -> throw IllegalArgumentException("Id can only be retrieved for Nodes or Relationships")
36+
}
37+
38+
fun String.toCamelCase(): String = Regex("[\\W_]([a-z])").replace(this) { it.groupValues[1].toUpperCase() }

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

Lines changed: 48 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@ import graphql.language.*
55
import graphql.schema.*
66
import org.neo4j.cypherdsl.core.Node
77
import org.neo4j.cypherdsl.core.Relationship
8+
import org.neo4j.cypherdsl.core.SymbolicName
89
import org.neo4j.graphql.DirectiveConstants.Companion.CYPHER
910
import org.neo4j.graphql.DirectiveConstants.Companion.CYPHER_STATEMENT
1011
import org.neo4j.graphql.DirectiveConstants.Companion.DYNAMIC
1112
import org.neo4j.graphql.DirectiveConstants.Companion.DYNAMIC_PREFIX
1213
import org.neo4j.graphql.DirectiveConstants.Companion.PROPERTY
1314
import org.neo4j.graphql.DirectiveConstants.Companion.PROPERTY_NAME
1415
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_DIRECTION
15-
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_DIRECTION_BOTH
16-
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_DIRECTION_IN
17-
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_DIRECTION_OUT
1816
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_FROM
1917
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_NAME
2018
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_TO
2119
import org.neo4j.graphql.handler.projection.ProjectionBase
20+
import java.math.BigDecimal
21+
import java.math.BigInteger
2222

2323
fun Type<Type<*>>.name(): String? = if (this.inner() is TypeName) (this.inner() as TypeName).name else null
2424
fun Type<Type<*>>.inner(): Type<Type<*>> = when (this) {
@@ -34,7 +34,7 @@ fun GraphQLType.inner(): GraphQLType = when (this) {
3434
}
3535

3636
fun GraphQLType.name(): String? = (this as? GraphQLNamedType)?.name
37-
fun GraphQLType.requiredName(): String = requireNotNull(name()) { -> "name is required but cannot be determined for " + this.javaClass }
37+
fun GraphQLType.requiredName(): String = requireNotNull(name()) { "name is required but cannot be determined for " + this.javaClass }
3838

3939
fun GraphQLType.isList() = this is GraphQLList || (this is GraphQLNonNull && this.wrappedType is GraphQLList)
4040
fun GraphQLType.isScalar() = this.inner().let { it is GraphQLScalarType || it.innerName().startsWith("_Neo4j") }
@@ -44,7 +44,6 @@ fun GraphQLFieldDefinition.isNeo4jType(): Boolean = this.type.isNeo4jType()
4444

4545
fun GraphQLFieldDefinition.isRelationship() = !type.isNeo4jType() && this.type.inner().let { it is GraphQLFieldsContainer }
4646

47-
fun GraphQLFieldsContainer.hasRelationship(name: String) = this.getFieldDefinition(name)?.isRelationship() ?: false
4847
fun GraphQLDirectiveContainer.isRelationType() = getDirective(DirectiveConstants.RELATION) != null
4948
fun GraphQLFieldsContainer.isRelationType() = (this as? GraphQLDirectiveContainer)?.getDirective(DirectiveConstants.RELATION) != null
5049
fun GraphQLFieldsContainer.relationshipFor(name: String): RelationshipInfo? {
@@ -69,16 +68,16 @@ fun GraphQLFieldsContainer.relationshipFor(name: String): RelationshipInfo? {
6968

7069
val relInfo = relDetails(fieldObjectType, relDirective)
7170

72-
return if (inverse) relInfo.copy(out = relInfo.out?.let { !it }, startField = relInfo.endField, endField = relInfo.startField) else relInfo
71+
return if (inverse) relInfo.copy(direction = relInfo.direction.invert(), startField = relInfo.endField, endField = relInfo.startField) else relInfo
7372
}
7473

7574
fun GraphQLFieldsContainer.getValidTypeLabels(schema: GraphQLSchema): List<String> {
7675
if (this is GraphQLObjectType) {
77-
return listOf(this.quotedLabel())
76+
return listOf(this.label())
7877
}
7978
if (this is GraphQLInterfaceType) {
8079
return schema.getImplementations(this)
81-
.mapNotNull { it.quotedLabel() }
80+
.mapNotNull { it.label() }
8281
}
8382
return emptyList()
8483
}
@@ -92,14 +91,6 @@ fun GraphQLFieldsContainer.label(): String = when {
9291
else -> name
9392
}
9493

95-
fun GraphQLFieldsContainer.quotedLabel() = this.label().quote()
96-
97-
fun GraphQLFieldsContainer.allLabels() = when {
98-
this.isRelationType() -> this.quotedLabel()
99-
else -> (listOf(name) + ((this as? GraphQLObjectType)?.interfaces?.map { it.name } ?: emptyList()))
100-
.map { it.quote() }
101-
.joinToString(":")
102-
}
10394

10495
fun GraphQLFieldsContainer.relevantFields() = fieldDefinitions
10596
.filter { it.type.isScalar() || it.isNeo4jType() }
@@ -113,7 +104,9 @@ fun GraphQLFieldsContainer.relationship(): RelationshipInfo? {
113104
val relType = directiveResolver(RELATION_NAME, "")!!
114105
val startField = directiveResolver(RELATION_FROM, null)
115106
val endField = directiveResolver(RELATION_TO, null)
116-
return RelationshipInfo(this, relType, null, startField, endField)
107+
val direction = directiveResolver(RELATION_DIRECTION, null)?.let { RelationDirection.valueOf(it) }
108+
?: RelationDirection.OUT
109+
return RelationshipInfo(this, relType, direction, startField, endField)
117110
}
118111

119112
fun GraphQLType.ref(): GraphQLType = when (this) {
@@ -127,23 +120,21 @@ fun GraphQLType.ref(): GraphQLType = when (this) {
127120

128121
fun relDetails(type: GraphQLFieldsContainer, relDirective: GraphQLDirective): RelationshipInfo {
129122
val relType = relDirective.getArgument(RELATION_NAME, "")!!
130-
val outgoing = when (relDirective.getArgument<String>(RELATION_DIRECTION, null)) {
131-
RELATION_DIRECTION_IN -> false
132-
RELATION_DIRECTION_BOTH -> null
133-
RELATION_DIRECTION_OUT -> true
134-
else -> throw IllegalStateException("Unknown direction ${relDirective.getArgument<String>(RELATION_DIRECTION, null)}")
135-
}
123+
val direction = relDirective.getArgument<String>(RELATION_DIRECTION, null)
124+
?.let { RelationDirection.valueOf(it) }
125+
?: RelationDirection.OUT
126+
136127
return RelationshipInfo(type,
137128
relType,
138-
outgoing,
129+
direction,
139130
relDirective.getArgument<String>(RELATION_FROM, null),
140131
relDirective.getArgument<String>(RELATION_TO, null))
141132
}
142133

143134
data class RelationshipInfo(
144135
val type: GraphQLFieldsContainer,
145136
val relType: String,
146-
val out: Boolean?,
137+
val direction: RelationDirection,
147138
val startField: String? = null,
148139
val endField: String? = null,
149140
val isRelFromType: Boolean = false
@@ -154,10 +145,10 @@ data class RelationshipInfo(
154145
val declaringType: GraphQLFieldsContainer
155146
)
156147

157-
val arrows = when (out) {
158-
false -> "<" to ""
159-
true -> "" to ">"
160-
null -> "" to ""
148+
val arrows = when (direction) {
149+
RelationDirection.IN -> "<" to ""
150+
RelationDirection.OUT -> "" to ">"
151+
RelationDirection.BOTH -> "" to ""
161152
}
162153

163154
val typeName: String get() = this.type.name
@@ -173,20 +164,26 @@ data class RelationshipInfo(
173164
val relType = relFieldDefinition.type.inner() as? GraphQLFieldsContainer
174165
?: throw IllegalArgumentException("type ${relFieldDefinition.type.innerName()} not found")
175166
return relType.fieldDefinitions.filter { it.isID() }
176-
.map { RelatedField("${relFieldName}_${it.name}", it, relType) }
167+
.map {
168+
// TODO b/c we need to stay backwards kompatible this is not caml case but with underscore
169+
//val filedName = normalizeName(relFieldName, it.name)
170+
val filedName = "${relFieldName}_${it.name}"
171+
RelatedField(filedName, it, relType)
172+
}
177173
.firstOrNull()
178174
}
179175

180176
fun createRelation(start: Node, end: Node): Relationship =
181-
when (this.out) {
182-
false -> start.relationshipFrom(end, this.relType)
183-
true -> start.relationshipTo(end, this.relType)
184-
null -> start.relationshipBetween(end, this.relType)
177+
when (this.direction) {
178+
RelationDirection.IN -> start.relationshipFrom(end, this.relType)
179+
RelationDirection.OUT -> start.relationshipTo(end, this.relType)
180+
RelationDirection.BOTH -> start.relationshipBetween(end, this.relType)
185181
}
186182
}
187183

188-
fun Field.aliasOrName() = (this.alias ?: this.name).quote()
189-
fun Field.contextualize(variable: String) = variable + (this.alias ?: this.name).capitalize()
184+
fun Field.aliasOrName(): String = (this.alias ?: this.name)
185+
fun Field.contextualize(variable: String) = variable + this.aliasOrName().capitalize()
186+
fun Field.contextualize(variable: SymbolicName) = variable.value + this.aliasOrName().capitalize()
190187

191188
fun GraphQLType.innerName(): String = inner().name()
192189
?: throw IllegalStateException("inner name cannot be retrieved for " + this.javaClass)
@@ -212,25 +209,6 @@ fun <T> GraphQLDirective.getArgument(argumentName: String, defaultValue: T?): T?
212209
fun GraphQLFieldDefinition.cypherDirective(): Cypher? = getDirectiveArgument<String>(CYPHER, CYPHER_STATEMENT, null)
213210
?.let { statement -> Cypher(statement) }
214211

215-
fun String.quote() = if (isJavaIdentifier()) this else "`$this`"
216-
217-
fun String.isJavaIdentifier() =
218-
this[0].isJavaIdentifierStart() &&
219-
this.substring(1).all { it.isJavaIdentifierPart() }
220-
221-
@Suppress("SimplifiableCallChain")
222-
fun Value<Value<*>>.toCypherString(): String = when (this) {
223-
is StringValue -> "'" + this.value + "'"
224-
is EnumValue -> "'" + this.name + "'"
225-
is NullValue -> "null"
226-
is BooleanValue -> this.isValue.toString()
227-
is FloatValue -> this.value.toString()
228-
is IntValue -> this.value.toString()
229-
is VariableReference -> "$" + this.name
230-
is ArrayValue -> this.values.map { it.toCypherString() }.joinToString(",", "[", "]")
231-
else -> throw IllegalStateException("Unhandled value $this")
232-
}
233-
234212
fun Any.toJavaValue() = when (this) {
235213
is Value<*> -> this.toJavaValue()
236214
else -> this
@@ -249,11 +227,6 @@ fun Value<*>.toJavaValue(): Any? = when (this) {
249227
else -> throw IllegalStateException("Unhandled value $this")
250228
}
251229

252-
fun paramName(variable: String, argName: String, value: Any?): String = when (value) {
253-
is VariableReference -> value.name
254-
else -> "$variable${argName.capitalize()}"
255-
}
256-
257230
fun GraphQLFieldDefinition.isID() = this.type.inner() == Scalars.GraphQLID
258231
fun GraphQLFieldDefinition.isNativeId() = this.name == ProjectionBase.NATIVE_ID
259232
fun GraphQLFieldsContainer.getIdField() = this.fieldDefinitions.find { it.isID() }
@@ -274,3 +247,18 @@ fun GraphQLInputObjectType.Builder.addFilterField(fieldName: String, isList: Boo
274247
fun GraphQLSchema.queryTypeName() = this.queryType?.name ?: "Query"
275248
fun GraphQLSchema.mutationTypeName() = this.mutationType?.name ?: "Mutation"
276249
fun GraphQLSchema.subscriptionTypeName() = this.subscriptionType?.name ?: "Subscription"
250+
251+
fun Any?.asGraphQLValue(): Value<*> = when (this) {
252+
null -> NullValue.newNullValue().build()
253+
is Value<*> -> this
254+
is Array<*> -> ArrayValue.newArrayValue().values(this.map { it.asGraphQLValue() }).build()
255+
is Iterable<*> -> ArrayValue.newArrayValue().values(this.map { it.asGraphQLValue() }).build()
256+
is Map<*, *> -> ObjectValue.newObjectValue().objectFields(this.map { entry -> ObjectField(entry.key as String, entry.value.asGraphQLValue()) }).build()
257+
is Enum<*> -> EnumValue.newEnumValue().name(this.name).build()
258+
is Int -> IntValue.newIntValue(BigInteger.valueOf(this.toLong())).build()
259+
is Long -> IntValue.newIntValue(BigInteger.valueOf(this)).build()
260+
is Number -> FloatValue.newFloatValue(BigDecimal.valueOf(this as Double)).build()
261+
is Boolean -> BooleanValue.newBooleanValue(this).build()
262+
is String -> StringValue.newStringValue(this).build()
263+
else -> throw IllegalStateException("Cannot convert ${this.javaClass.name} into an graphql type")
264+
}

0 commit comments

Comments
 (0)