Skip to content
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

Add support for neo4j 5.x #282

Merged
merged 1 commit into from
Dec 12, 2022
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
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
18 changes: 12 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,15 +73,16 @@
<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>
<!-- the old version of junit is required to have the nice diff handling in intellij -->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
Expand Down Expand Up @@ -110,7 +116,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
7 changes: 4 additions & 3 deletions core/src/test/kotlin/GraphQLServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import org.neo4j.driver.GraphDatabase
import org.neo4j.driver.Values
import org.neo4j.graphql.*
import java.net.InetSocketAddress
import kotlin.streams.toList
import java.util.stream.Collectors


const val schema = """
Expand Down Expand Up @@ -59,7 +59,7 @@ fun main() {
try {
val result = session.run(cypher, Values.value(params))
when {
type?.isList() == true -> result.stream().map { it[variable].asObject() }.toList()
type?.isList() == true -> result.stream().map { it[variable].asObject() }.collect(Collectors.toList())
else -> result.stream().map { it[variable].asObject() }.findFirst().orElse(emptyMap<String, Any>())
}
} catch (e: Exception) {
Expand All @@ -83,10 +83,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