Skip to content

Commit 117b058

Browse files
authored
Add support for neo4j 5.x (#282)
resolves #281
1 parent 15ac500 commit 117b058

File tree

20 files changed

+106
-65
lines changed

20 files changed

+106
-65
lines changed

.github/workflows/pr-build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Setup Java JDK
1818
uses: actions/setup-java@v2.0.0
1919
with:
20-
java-version: 11
20+
java-version: 17
2121
distribution: adopt
2222
- name: Run Maven build
2323
run: ./mvnw --no-transfer-progress -Dneo4j-graphql-java.integration-tests=true -Dneo4j-graphql-java.generate-test-file-diff=false -Dneo4j-graphql-java.flatten-tests=true clean compile test

core/pom.xml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<dependency>
1919
<groupId>org.neo4j.driver</groupId>
2020
<artifactId>neo4j-java-driver</artifactId>
21-
<version>4.4.9</version>
21+
<version>5.2.0</version>
2222
<scope>test</scope>
2323
</dependency>
2424
<dependency>
@@ -35,9 +35,14 @@
3535
</dependency>
3636
<dependency>
3737
<groupId>org.neo4j.procedure</groupId>
38-
<artifactId>apoc</artifactId>
38+
<artifactId>apoc-core</artifactId>
39+
<version>${neo4j-apoc.version}</version>
40+
<scope>test</scope>
41+
</dependency>
42+
<dependency>
43+
<groupId>org.neo4j.procedure</groupId>
44+
<artifactId>apoc-common</artifactId>
3945
<version>${neo4j-apoc.version}</version>
40-
<classifier>all</classifier>
4146
<scope>test</scope>
4247
</dependency>
4348
<dependency>
@@ -68,15 +73,16 @@
6873
<dependency>
6974
<groupId>org.neo4j</groupId>
7075
<artifactId>neo4j-cypher-dsl</artifactId>
71-
<version>2022.7.2</version>
76+
<version>2022.8.1</version>
7277
</dependency>
7378
<dependency>
7479
<groupId>ch.qos.logback</groupId>
7580
<artifactId>logback-classic</artifactId>
76-
<version>1.4.0</version>
81+
<version>1.4.5</version>
7782
<scope>test</scope>
7883
</dependency>
7984
<dependency>
85+
<!-- the old version of junit is required to have the nice diff handling in intellij -->
8086
<groupId>junit</groupId>
8187
<artifactId>junit</artifactId>
8288
<version>4.13.2</version>
@@ -110,7 +116,7 @@
110116
<dependency>
111117
<groupId>org.slf4j</groupId>
112118
<artifactId>slf4j-api</artifactId>
113-
<version>2.0.0</version>
119+
<version>2.0.5</version>
114120
</dependency>
115121
</dependencies>
116122
</dependencyManagement>

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

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

3+
import graphql.GraphQLContext
34
import graphql.Scalars
45
import graphql.language.*
56
import graphql.language.TypeDefinition
@@ -94,6 +95,7 @@ fun GraphQLFieldsContainer.label(): String = when {
9495
?.getDirective(DirectiveConstants.RELATION)
9596
?.getArgument(RELATION_NAME)?.argumentValue?.value?.toJavaValue()?.toString()
9697
?: this.name
98+
9799
else -> name
98100
}
99101

@@ -232,6 +234,11 @@ fun DataFetchingEnvironment.typeAsContainer() = this.fieldDefinition.type.inner(
232234
?: throw IllegalStateException("expect type of field ${this.logField()} to be GraphQLFieldsContainer, but was ${this.fieldDefinition.type.name()}")
233235

234236
fun DataFetchingEnvironment.logField() = "${this.parentType.name()}.${this.fieldDefinition.name}"
237+
fun DataFetchingEnvironment.queryContext(): QueryContext = this.graphQlContext.get(QueryContext.KEY) ?: QueryContext()
238+
fun GraphQLContext.setQueryContext(ctx: QueryContext): GraphQLContext {
239+
this.put(QueryContext.KEY, ctx)
240+
return this
241+
}
235242

236243
val TypeInt = TypeName("Int")
237244
val TypeFloat = TypeName("Float")

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

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

3+
import org.neo4j.cypherdsl.core.renderer.Dialect
4+
35
data class QueryContext @JvmOverloads constructor(
46
/**
57
* if true the <code>__typename</code> will always be returned for interfaces, no matter if it was queried or not
@@ -9,7 +11,9 @@ data class QueryContext @JvmOverloads constructor(
911
/**
1012
* If set alternative approaches for query translation will be used
1113
*/
12-
var optimizedQuery: Set<OptimizationStrategy>? = null
14+
var optimizedQuery: Set<OptimizationStrategy>? = null,
15+
16+
var neo4jDialect: Dialect = Dialect.NEO4J_5
1317

1418
) {
1519
enum class OptimizationStrategy {
@@ -18,4 +22,8 @@ data class QueryContext @JvmOverloads constructor(
1822
*/
1923
FILTER_AS_MATCH
2024
}
25+
26+
companion object {
27+
const val KEY = "Neo4jGraphQLQueryContext"
28+
}
2129
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import graphql.schema.GraphQLSchema
66

77
class Translator(val schema: GraphQLSchema) {
88

9-
class CypherHolder(val cyphers :MutableList<Cypher> = mutableListOf())
9+
class CypherHolder(val cyphers: MutableList<Cypher> = mutableListOf())
1010

1111
private val gql: GraphQL = GraphQL.newGraphQL(schema).build()
1212

@@ -17,7 +17,7 @@ class Translator(val schema: GraphQLSchema) {
1717
val executionInput = ExecutionInput.newExecutionInput()
1818
.query(query)
1919
.variables(params)
20-
.context(ctx)
20+
.graphQLContext(mapOf(QueryContext.KEY to ctx))
2121
.localContext(cypherHolder)
2222
.build()
2323
val result = gql.execute(executionInput)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ abstract class BaseDataFetcher(schemaConfig: SchemaConfig) : ProjectionBase(sche
2525
val variable = field.aliasOrName().decapitalize()
2626
prepareDataFetcher(env.fieldDefinition, env.parentType)
2727
val statement = generateCypher(variable, field, env)
28-
28+
val dialect = env.queryContext().neo4jDialect
2929
val query = Renderer.getRenderer(Configuration
3030
.newConfig()
3131
.withIndentStyle(Configuration.IndentStyle.TAB)
3232
.withPrettyPrint(true)
33+
.withDialect(dialect)
3334
.build()
3435
).render(statement)
3536

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,12 @@ class QueryHandler private constructor(schemaConfig: SchemaConfig) : BaseDataFet
107107
val (propertyContainer, match) = when {
108108
type.isRelationType() -> anyNode().relationshipTo(anyNode(), type.label()).named(variable)
109109
.let { rel -> rel to match(rel) }
110+
110111
else -> node(type.label()).named(variable)
111112
.let { node -> node to match(node) }
112113
}
113114

114-
val ongoingReading = if ((env.getContext() as? QueryContext)?.optimizedQuery?.contains(QueryContext.OptimizationStrategy.FILTER_AS_MATCH) == true) {
115+
val ongoingReading = if (env.queryContext().optimizedQuery?.contains(QueryContext.OptimizationStrategy.FILTER_AS_MATCH) == true) {
115116

116117
OptimizedFilterHandler(type, schemaConfig).generateFilterQuery(variable, fieldDefinition, env.arguments, match, propertyContainer, env.variables)
117118

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ open class ProjectionBase(
225225
values.size > 1 -> values.mapIndexed { index, value -> handleLogicalOperator(value, "${classifier}${index + 1}", variables) }
226226
else -> values.map { value -> handleLogicalOperator(value, "", variables) }
227227
}
228+
228229
else -> emptyList()
229230
}
230231
}
@@ -275,9 +276,8 @@ open class ProjectionBase(
275276
projections.addAll(pro)
276277
subQueries += sub
277278
}
278-
if (nodeType is GraphQLInterfaceType
279-
&& !handledFields.contains(TYPE_NAME)
280-
&& (env.getContext() as? QueryContext)?.queryTypeOfInterfaces == true
279+
if ((nodeType is GraphQLInterfaceType
280+
&& !handledFields.contains(TYPE_NAME)) && env.queryContext().queryTypeOfInterfaces
281281
) {
282282
// for interfaces the typename is required to determine the correct implementation
283283
val (pro, sub) = projectField(propertyContainer, variable, TYPE_NAME_SELECTED_FIELD, nodeType, env, variableSuffix)
@@ -348,6 +348,7 @@ open class ProjectionBase(
348348
schemaConfig.useTemporalScalars && fieldDefinition.isNeo4jTemporalType() -> {
349349
projections += getNeo4jTypeConverter(fieldDefinition).projectField(variable, field, "")
350350
}
351+
351352
isObjectField -> {
352353
if (fieldDefinition.isNeo4jType()) {
353354
projections += projectNeo4jObjectType(variable, field, fieldDefinition)
@@ -357,9 +358,11 @@ open class ProjectionBase(
357358
subQueries += sub
358359
}
359360
}
361+
360362
fieldDefinition.isNativeId() -> {
361363
projections += id(anyNode(variable))
362364
}
365+
363366
else -> {
364367
val dynamicPrefix = fieldDefinition.dynamicPrefix()
365368
when {
@@ -376,6 +379,7 @@ open class ProjectionBase(
376379
)
377380
.asFunction()
378381
}
382+
379383
field.aliasOrName() != fieldDefinition.propertyName() -> {
380384
projections += variable.property(fieldDefinition.propertyName())
381385
}
@@ -481,6 +485,7 @@ open class ProjectionBase(
481485
?: "")), variableSuffix, field.selectionSet)
482486
propertyContainer.project(projectionEntries) to subQueries
483487
}
488+
484489
is Relationship -> projectNodeFromRichRelationship(parent, fieldDefinition, variable, field, env)
485490
else -> throw IllegalArgumentException("${propertyContainer.javaClass.name} cannot be handled for field ${fieldDefinition.name} of type ${parent.name}")
486491
}
@@ -543,6 +548,7 @@ open class ProjectionBase(
543548
val label = nodeType.getRelevantFieldDefinition(relInfo.endField)!!.type.innerName()
544549
node(label).named("$childVariable${relInfo.endField.capitalize()}") to relInfo.endField
545550
}
551+
546552
else -> node(nodeType.name).named(childVariableName) to null
547553
}
548554

core/src/test/kotlin/DataFetcherInterceptorDemo.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ package demo
33
import graphql.GraphQL
44
import graphql.schema.*
55
import org.intellij.lang.annotations.Language
6+
import org.neo4j.cypherdsl.core.renderer.Dialect
67
import org.neo4j.driver.AuthTokens
78
import org.neo4j.driver.Driver
89
import org.neo4j.driver.GraphDatabase
9-
import org.neo4j.graphql.Cypher
10-
import org.neo4j.graphql.DataFetchingInterceptor
11-
import org.neo4j.graphql.SchemaBuilder
10+
import org.neo4j.graphql.*
1211
import java.math.BigDecimal
1312
import java.math.BigInteger
1413

@@ -18,6 +17,7 @@ fun initBoundSchema(schema: String): GraphQLSchema {
1817

1918
val dataFetchingInterceptor = object : DataFetchingInterceptor {
2019
override fun fetchData(env: DataFetchingEnvironment, delegate: DataFetcher<Cypher>): Any {
20+
env.graphQlContext.setQueryContext(QueryContext(neo4jDialect = Dialect.DEFAULT))
2121
val (cypher, params, type, variable) = delegate.get(env)
2222
return driver.session().use { session ->
2323
val result = session.run(cypher, params.mapValues { toBoltValue(it.value) })

core/src/test/kotlin/GraphQLServer.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import org.neo4j.driver.GraphDatabase
1717
import org.neo4j.driver.Values
1818
import org.neo4j.graphql.*
1919
import java.net.InetSocketAddress
20-
import kotlin.streams.toList
20+
import java.util.stream.Collectors
2121

2222

2323
const val schema = """
@@ -59,7 +59,7 @@ fun main() {
5959
try {
6060
val result = session.run(cypher, Values.value(params))
6161
when {
62-
type?.isList() == true -> result.stream().map { it[variable].asObject() }.toList()
62+
type?.isList() == true -> result.stream().map { it[variable].asObject() }.collect(Collectors.toList())
6363
else -> result.stream().map { it[variable].asObject() }.findFirst().orElse(emptyMap<String, Any>())
6464
}
6565
} catch (e: Exception) {
@@ -83,10 +83,11 @@ fun main() {
8383
schema.execute(query).let { println(mapper.writeValueAsString(it));it }
8484
} else {
8585
try {
86+
val queryContext = QueryContext(optimizedQuery = setOf(QueryContext.OptimizationStrategy.FILTER_AS_MATCH))
8687
schema.execute(ExecutionInput
8788
.newExecutionInput()
8889
.query(query)
89-
.context(QueryContext(optimizedQuery = setOf(QueryContext.OptimizationStrategy.FILTER_AS_MATCH)))
90+
.graphQLContext(mapOf(QueryContext.KEY to queryContext))
9091
.variables(params(payload))
9192
.build())
9293
} catch (e: OptimizedQueryException) {

0 commit comments

Comments
 (0)