Skip to content

Commit

Permalink
Add support for neo4j 5.x
Browse files Browse the repository at this point in the history
resolves #281
  • Loading branch information
Andy2003 committed Dec 12, 2022
1 parent f37f1de commit 81eb02f
Show file tree
Hide file tree
Showing 20 changed files with 104 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pr-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Setup Java JDK
uses: actions/[email protected]
with:
java-version: 11
java-version: 17
distribution: adopt
- name: Run Maven build
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
Expand Down
17 changes: 11 additions & 6 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>4.4.9</version>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -35,9 +35,14 @@
</dependency>
<dependency>
<groupId>org.neo4j.procedure</groupId>
<artifactId>apoc</artifactId>
<artifactId>apoc-core</artifactId>
<version>${neo4j-apoc.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j.procedure</groupId>
<artifactId>apoc-common</artifactId>
<version>${neo4j-apoc.version}</version>
<classifier>all</classifier>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -68,12 +73,12 @@
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-cypher-dsl</artifactId>
<version>2022.7.2</version>
<version>2022.8.1</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.0</version>
<version>1.4.5</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -110,7 +115,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.0</version>
<version>2.0.5</version>
</dependency>
</dependencies>
</dependencyManagement>
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/kotlin/org/neo4j/graphql/GraphQLExtensions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.neo4j.graphql

import graphql.GraphQLContext
import graphql.Scalars
import graphql.language.*
import graphql.language.TypeDefinition
Expand Down Expand Up @@ -94,6 +95,7 @@ fun GraphQLFieldsContainer.label(): String = when {
?.getDirective(DirectiveConstants.RELATION)
?.getArgument(RELATION_NAME)?.argumentValue?.value?.toJavaValue()?.toString()
?: this.name

else -> name
}

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

fun DataFetchingEnvironment.logField() = "${this.parentType.name()}.${this.fieldDefinition.name}"
fun DataFetchingEnvironment.queryContext(): QueryContext = this.graphQlContext.get(QueryContext.KEY) ?: QueryContext()
fun GraphQLContext.setQueryContext(ctx: QueryContext): GraphQLContext {
this.put(QueryContext.KEY, ctx)
return this
}

val TypeInt = TypeName("Int")
val TypeFloat = TypeName("Float")
Expand Down
10 changes: 9 additions & 1 deletion core/src/main/kotlin/org/neo4j/graphql/QueryContext.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.neo4j.graphql

import org.neo4j.cypherdsl.core.renderer.Dialect

data class QueryContext @JvmOverloads constructor(
/**
* if true the <code>__typename</code> will always be returned for interfaces, no matter if it was queried or not
Expand All @@ -9,7 +11,9 @@ data class QueryContext @JvmOverloads constructor(
/**
* If set alternative approaches for query translation will be used
*/
var optimizedQuery: Set<OptimizationStrategy>? = null
var optimizedQuery: Set<OptimizationStrategy>? = null,

var neo4jDialect: Dialect = Dialect.NEO4J_5

) {
enum class OptimizationStrategy {
Expand All @@ -18,4 +22,8 @@ data class QueryContext @JvmOverloads constructor(
*/
FILTER_AS_MATCH
}

companion object {
const val KEY = "Neo4jGraphQLQueryContext"
}
}
4 changes: 2 additions & 2 deletions core/src/main/kotlin/org/neo4j/graphql/Translator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import graphql.schema.GraphQLSchema

class Translator(val schema: GraphQLSchema) {

class CypherHolder(val cyphers :MutableList<Cypher> = mutableListOf())
class CypherHolder(val cyphers: MutableList<Cypher> = mutableListOf())

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

Expand All @@ -17,7 +17,7 @@ class Translator(val schema: GraphQLSchema) {
val executionInput = ExecutionInput.newExecutionInput()
.query(query)
.variables(params)
.context(ctx)
.graphQLContext(mapOf(QueryContext.KEY to ctx))
.localContext(cypherHolder)
.build()
val result = gql.execute(executionInput)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ abstract class BaseDataFetcher(schemaConfig: SchemaConfig) : ProjectionBase(sche
val variable = field.aliasOrName().decapitalize()
prepareDataFetcher(env.fieldDefinition, env.parentType)
val statement = generateCypher(variable, field, env)

val dialect = env.queryContext().neo4jDialect
val query = Renderer.getRenderer(Configuration
.newConfig()
.withIndentStyle(Configuration.IndentStyle.TAB)
.withPrettyPrint(true)
.withDialect(dialect)
.build()
).render(statement)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,12 @@ class QueryHandler private constructor(schemaConfig: SchemaConfig) : BaseDataFet
val (propertyContainer, match) = when {
type.isRelationType() -> anyNode().relationshipTo(anyNode(), type.label()).named(variable)
.let { rel -> rel to match(rel) }

else -> node(type.label()).named(variable)
.let { node -> node to match(node) }
}

val ongoingReading = if ((env.getContext() as? QueryContext)?.optimizedQuery?.contains(QueryContext.OptimizationStrategy.FILTER_AS_MATCH) == true) {
val ongoingReading = if (env.queryContext().optimizedQuery?.contains(QueryContext.OptimizationStrategy.FILTER_AS_MATCH) == true) {

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ open class ProjectionBase(
values.size > 1 -> values.mapIndexed { index, value -> handleLogicalOperator(value, "${classifier}${index + 1}", variables) }
else -> values.map { value -> handleLogicalOperator(value, "", variables) }
}

else -> emptyList()
}
}
Expand Down Expand Up @@ -267,9 +268,8 @@ open class ProjectionBase(
projections.addAll(pro)
subQueries += sub
}
if (nodeType is GraphQLInterfaceType
&& !handledFields.contains(TYPE_NAME)
&& (env.getContext() as? QueryContext)?.queryTypeOfInterfaces == true
if ((nodeType is GraphQLInterfaceType
&& !handledFields.contains(TYPE_NAME)) && env.queryContext().queryTypeOfInterfaces
) {
// for interfaces the typename is required to determine the correct implementation
val (pro, sub) = projectField(propertyContainer, variable, TYPE_NAME_SELECTED_FIELD, nodeType, env, variableSuffix)
Expand Down Expand Up @@ -340,6 +340,7 @@ open class ProjectionBase(
schemaConfig.useTemporalScalars && fieldDefinition.isNeo4jTemporalType() -> {
projections += getNeo4jTypeConverter(fieldDefinition).projectField(variable, field, "")
}

isObjectField -> {
if (fieldDefinition.isNeo4jType()) {
projections += projectNeo4jObjectType(variable, field, fieldDefinition)
Expand All @@ -349,9 +350,11 @@ open class ProjectionBase(
subQueries += sub
}
}

fieldDefinition.isNativeId() -> {
projections += id(anyNode(variable))
}

else -> {
val dynamicPrefix = fieldDefinition.dynamicPrefix()
when {
Expand All @@ -368,6 +371,7 @@ open class ProjectionBase(
)
.asFunction()
}

field.aliasOrName() != fieldDefinition.propertyName() -> {
projections += variable.property(fieldDefinition.propertyName())
}
Expand Down Expand Up @@ -473,6 +477,7 @@ open class ProjectionBase(
?: "")), variableSuffix, field.selectionSet)
propertyContainer.project(projectionEntries) to subQueries
}

is Relationship -> projectNodeFromRichRelationship(parent, fieldDefinition, variable, field, env)
else -> throw IllegalArgumentException("${propertyContainer.javaClass.name} cannot be handled for field ${fieldDefinition.name} of type ${parent.name}")
}
Expand Down Expand Up @@ -535,6 +540,7 @@ open class ProjectionBase(
val label = nodeType.getRelevantFieldDefinition(relInfo.endField)!!.type.innerName()
node(label).named("$childVariable${relInfo.endField.capitalize()}") to relInfo.endField
}

else -> node(nodeType.name).named(childVariableName) to null
}

Expand Down
6 changes: 3 additions & 3 deletions core/src/test/kotlin/DataFetcherInterceptorDemo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ package demo
import graphql.GraphQL
import graphql.schema.*
import org.intellij.lang.annotations.Language
import org.neo4j.cypherdsl.core.renderer.Dialect
import org.neo4j.driver.AuthTokens
import org.neo4j.driver.Driver
import org.neo4j.driver.GraphDatabase
import org.neo4j.graphql.Cypher
import org.neo4j.graphql.DataFetchingInterceptor
import org.neo4j.graphql.SchemaBuilder
import org.neo4j.graphql.*
import java.math.BigDecimal
import java.math.BigInteger

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

val dataFetchingInterceptor = object : DataFetchingInterceptor {
override fun fetchData(env: DataFetchingEnvironment, delegate: DataFetcher<Cypher>): Any {
env.graphQlContext.setQueryContext(QueryContext(neo4jDialect = Dialect.DEFAULT))
val (cypher, params, type, variable) = delegate.get(env)
return driver.session().use { session ->
val result = session.run(cypher, params.mapValues { toBoltValue(it.value) })
Expand Down
4 changes: 2 additions & 2 deletions core/src/test/kotlin/GraphQLServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import org.neo4j.driver.GraphDatabase
import org.neo4j.driver.Values
import org.neo4j.graphql.*
import java.net.InetSocketAddress
import kotlin.streams.toList


const val schema = """
Expand Down Expand Up @@ -83,10 +82,11 @@ fun main() {
schema.execute(query).let { println(mapper.writeValueAsString(it));it }
} else {
try {
val queryContext = QueryContext(optimizedQuery = setOf(QueryContext.OptimizationStrategy.FILTER_AS_MATCH))
schema.execute(ExecutionInput
.newExecutionInput()
.query(query)
.context(QueryContext(optimizedQuery = setOf(QueryContext.OptimizationStrategy.FILTER_AS_MATCH)))
.graphQLContext(mapOf(QueryContext.KEY to queryContext))
.variables(params(payload))
.build())
} catch (e: OptimizedQueryException) {
Expand Down
10 changes: 5 additions & 5 deletions core/src/test/resources/filter-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2431,7 +2431,7 @@ RETURN person {
[source,cypher]
----
MATCH (person:Person)
WHERE distance(person.location, point($filterPersonLocationDistance.point)) = $filterPersonLocationDistance.distance
WHERE point.distance(person.location, point($filterPersonLocationDistance.point)) = $filterPersonLocationDistance.distance
RETURN person {
.name
} AS person
Expand Down Expand Up @@ -2480,7 +2480,7 @@ RETURN person {
[source,cypher]
----
MATCH (person:Person)
WHERE distance(person.location, point($filterPersonLocationDistanceLt.point)) < $filterPersonLocationDistanceLt.distance
WHERE point.distance(person.location, point($filterPersonLocationDistanceLt.point)) < $filterPersonLocationDistanceLt.distance
RETURN person {
.name
} AS person
Expand Down Expand Up @@ -2529,7 +2529,7 @@ RETURN person {
[source,cypher]
----
MATCH (person:Person)
WHERE distance(person.location, point($filterPersonLocationDistanceLte.point)) <= $filterPersonLocationDistanceLte.distance
WHERE point.distance(person.location, point($filterPersonLocationDistanceLte.point)) <= $filterPersonLocationDistanceLte.distance
RETURN person {
.name
} AS person
Expand Down Expand Up @@ -2578,7 +2578,7 @@ RETURN person {
[source,cypher]
----
MATCH (person:Person)
WHERE distance(person.location, point($filterPersonLocationDistanceGt.point)) > $filterPersonLocationDistanceGt.distance
WHERE point.distance(person.location, point($filterPersonLocationDistanceGt.point)) > $filterPersonLocationDistanceGt.distance
RETURN person {
.name
} AS person
Expand Down Expand Up @@ -2627,7 +2627,7 @@ RETURN person {
[source,cypher]
----
MATCH (person:Person)
WHERE distance(person.location, point($filterPersonLocationDistanceGte.point)) >= $filterPersonLocationDistanceGte.distance
WHERE point.distance(person.location, point($filterPersonLocationDistanceGte.point)) >= $filterPersonLocationDistanceGte.distance
RETURN person {
.name
} AS person
Expand Down
2 changes: 1 addition & 1 deletion core/src/test/resources/optimized-query-for-filter.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,7 @@ RETURN person {
[source,cypher]
----
MATCH (person:Person)
WHERE distance(person.location, point($personLocationDistanceLt.point)) < $personLocationDistanceLt.distance
WHERE point.distance(person.location, point($personLocationDistanceLt.point)) < $personLocationDistanceLt.distance
WITH person
RETURN person {
.name
Expand Down
12 changes: 9 additions & 3 deletions examples/dgs-spring-boot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
<description>Example for using neo4j-graphql-java with Spring Boot and Netflix Domain Graph Service (DGS)</description>

<properties>
<testcontainers.version>1.17.3</testcontainers.version>
<spring-boot.version>2.7.3</spring-boot.version>
<testcontainers.version>1.17.6</testcontainers.version>
<spring-boot.version>2.7.6</spring-boot.version>
<graphql-java.version>19.2</graphql-java.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -46,7 +47,7 @@
<dependency>
<groupId>com.netflix.graphql.dgs</groupId>
<artifactId>graphql-dgs-spring-boot-starter</artifactId>
<version>5.2.1</version>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>com.netflix.graphql.dgs.codegen</groupId>
Expand Down Expand Up @@ -90,6 +91,11 @@

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>${graphql-java.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package org.neo4j.graphql.examples.dgsspringboot.config

import graphql.schema.*
import org.neo4j.cypherdsl.core.renderer.Dialect
import org.neo4j.driver.Driver
import org.neo4j.driver.SessionConfig
import org.neo4j.graphql.Cypher
import org.neo4j.graphql.DataFetchingInterceptor
import org.neo4j.graphql.QueryContext
import org.neo4j.graphql.setQueryContext
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
Expand All @@ -25,6 +28,8 @@ open class Neo4jConfiguration {
open fun dataFetchingInterceptor(driver: Driver, @Value("\${database}") database: String): DataFetchingInterceptor {
return object : DataFetchingInterceptor {
override fun fetchData(env: DataFetchingEnvironment, delegate: DataFetcher<Cypher>): Any? {
// here you can switch to the new neo4j 5 dialect, if required
env.graphQlContext.setQueryContext(QueryContext(neo4jDialect = Dialect.DEFAULT))
val (cypher, params, type, variable) = delegate.get(env)

return driver.session(SessionConfig.forDatabase(database)).writeTransaction { tx ->
Expand Down
Loading

0 comments on commit 81eb02f

Please sign in to comment.