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

update to neo4j 4.2 #200

Merged
merged 1 commit into from
Apr 14, 2021
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
61 changes: 16 additions & 45 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,23 @@
<description>GraphQL to Cypher Mapping</description>

<dependencies>
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j.test</groupId>
<artifactId>neo4j-harness</artifactId>
<version>${neo4j.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>slf4j-nop</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.neo4j.procedure</groupId>
<artifactId>apoc</artifactId>
<version>${neo4j-apoc.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>server-api</artifactId>
<version>${neo4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
<classifier>all</classifier>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -66,16 +44,9 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.12.2</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -94,19 +65,19 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>${driver.version}</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit-jupiter.version}</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-kernel</artifactId>
<version>${neo4j.version}</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.5.1</version>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.19.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ abstract class BaseDataFetcher(val fieldDefinition: GraphQLFieldDefinition) : Pr
.withPrettyPrint(true)
.build()
).render(statement)
return Cypher(query, statement.parameters, fieldDefinition.type, variable = variable)
return Cypher(query, statement.parameters, fieldDefinition.type, variable = field.aliasOrName())
}

protected abstract fun generateCypher(variable: String, field: Field, env: DataFetchingEnvironment): Statement
Expand Down
23 changes: 11 additions & 12 deletions core/src/test/kotlin/DataFetcherInterceptorDemo.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package demo

import graphql.GraphQL
import graphql.language.VariableReference
import graphql.schema.*
import org.intellij.lang.annotations.Language
import org.neo4j.driver.v1.AuthTokens
import org.neo4j.driver.v1.GraphDatabase
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
Expand All @@ -14,19 +14,19 @@ import java.math.BigInteger


fun initBoundSchema(schema: String): GraphQLSchema {
val driver = GraphDatabase.driver("bolt://localhost", AuthTokens.basic("neo4j", "test"))
val driver: Driver = GraphDatabase.driver("bolt://localhost", AuthTokens.basic("neo4j", "test"))

val dataFetchingInterceptor = object : DataFetchingInterceptor {
override fun fetchData(env: DataFetchingEnvironment, delegate: DataFetcher<Cypher>): Any {
val cypher = delegate.get(env)
val (cypher, params, type, variable) = delegate.get(env)
return driver.session().use { session ->
val result = session.run(cypher.query, cypher.params.mapValues { toBoltValue(it.value, env.variables) })
if (isListType(cypher.type)) {
result.list().map { record -> record.get(cypher.variable).asObject() }
val result = session.run(cypher, params.mapValues { toBoltValue(it.value) })
if (isListType(type)) {
result.list().map { record -> record.get(variable).asObject() }

} else {
result.list().map { record -> record.get(cypher.variable).asObject() }
.firstOrNull() ?: emptyMap<String, Any>()
result.list().map { record -> record.get(variable).asObject() }.firstOrNull()
?: emptyMap<String, Any>()
}
}
}
Expand All @@ -45,8 +45,7 @@ fun main() {
val movies = graphql.execute("{ movie { title }}")
}

fun toBoltValue(value: Any?, params: Map<String, Any?>) = when (value) {
is VariableReference -> params[value.name]
fun toBoltValue(value: Any?) = when (value) {
is BigInteger -> value.longValueExact()
is BigDecimal -> value.toDouble()
else -> value
Expand Down
126 changes: 73 additions & 53 deletions core/src/test/kotlin/GraphQLServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ package demo
// curl -H content-type:application/json -d'{"query": "{ movie { title, released }}"}' http://localhost:4567/graphql
// GraphiQL: https://neo4j-graphql.github.io/graphiql4all/index.html?graphqlEndpoint=http%3A%2F%2Flocalhost%3A4567%2Fgraphql&query=query%20%7B%0A%20%20movie%20%7B%20title%7D%0A%7D

import com.google.gson.Gson
import com.fasterxml.jackson.databind.ObjectMapper
import com.sun.net.httpserver.HttpExchange
import com.sun.net.httpserver.HttpServer
import graphql.ExecutionInput
import graphql.GraphQL
import org.neo4j.driver.v1.AuthTokens
import org.neo4j.driver.v1.Config
import org.neo4j.driver.v1.GraphDatabase
import org.neo4j.driver.v1.Values
import graphql.schema.DataFetcher
import graphql.schema.DataFetchingEnvironment
import org.neo4j.driver.AuthTokens
import org.neo4j.driver.Config
import org.neo4j.driver.GraphDatabase
import org.neo4j.driver.Values
import org.neo4j.graphql.*
import spark.Request
import spark.Response
import spark.Spark
import java.util.*
import java.net.InetSocketAddress
import kotlin.streams.toList


const val schema = """
type Person {
Expand All @@ -29,69 +33,85 @@ type Movie {
}
"""

val mapper = ObjectMapper()

fun main() {
val gson = Gson()
fun render(value: Any) = gson.toJson(value)
fun parseBody(value: String) = gson.fromJson(value, Map::class.java)

fun query(payload: Map<*, *>) = (payload["query"]!! as String).also { println(it) }
fun params(payload: Map<*, *>): Map<String, Any?> = payload["variables"]
.let {
@Suppress("UNCHECKED_CAST")
when (it) {
is String -> if (it.isBlank()) emptyMap<String, Any?>() else gson.fromJson(it, Map::class.java)
is String -> if (it.isBlank()) emptyMap<String, Any?>() else mapper.readValue(it, Map::class.java)
is Map<*, *> -> it
else -> emptyMap<String, Any?>()
} as Map<String, Any?>
}.also { println(it) }

val driver = GraphDatabase.driver("bolt://localhost", AuthTokens.basic("neo4j", "test"), Config.builder().withoutEncryption().build())

val graphQLSchema = SchemaBuilder.buildSchema(schema, dataFetchingInterceptor = object : DataFetchingInterceptor {
override fun fetchData(env: DataFetchingEnvironment, delegate: DataFetcher<Cypher>): Any? {
val (cypher, params, type, variable) = delegate.get(env)
println(cypher)
println(params)
return driver.session().use { session ->
try {
val result = session.run(cypher, Values.value(params))
when {
type?.isList() == true -> result.stream().map { it[variable].asObject() }.toList()
else -> result.stream().map { it[variable].asObject() }.findFirst().orElse(emptyMap<String, Any>())
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
})

val graphQLSchema = SchemaBuilder.buildSchema(schema)
println(graphQLSchema)
val schema = GraphQL.newGraphQL(graphQLSchema).build()
val translator = Translator(graphQLSchema)
fun translate(query: String, params: Map<String, Any?>) = try {
val ctx = QueryContext(optimizedQuery = setOf(QueryContext.OptimizationStrategy.FILTER_AS_MATCH))
translator.translate(query, params, ctx)
} catch (e: OptimizedQueryException) {
translator.translate(query, params)
}

val driver = GraphDatabase.driver("bolt://localhost", AuthTokens.basic("neo4j", "test"), Config.build().withoutEncryption().build())
fun run(cypher: Cypher) = driver.session().use {
println(cypher.query)
println(cypher.params)
try {
// todo fix parameter mapping in translator
val result = it.run(cypher.query, Values.value(cypher.params))
val value = if (cypher.type?.isList() == true) {
result.list().map { row -> row.get(cypher.variable).asObject() }
} else {
result.list().map { record -> record.get(cypher.variable).asObject() }
.firstOrNull() ?: emptyMap<String, Any>()
val server: HttpServer = HttpServer.create(InetSocketAddress(4567), 0)

server.createContext("/graphql") { req ->
when {
req.requestMethod == "OPTIONS" -> req.sendResponse(null)
req.requestMethod == "POST" && req.requestHeaders["Content-Type"]?.contains("application/json") == true -> {
val payload = mapper.readValue(req.requestBody, Map::class.java)
val query = query(payload)
val response = if (query.contains("__schema")) {
schema.execute(query).let { println(mapper.writeValueAsString(it));it }
} else {
try {
schema.execute(ExecutionInput
.newExecutionInput()
.query(query)
.context(QueryContext(optimizedQuery = setOf(QueryContext.OptimizationStrategy.FILTER_AS_MATCH)))
.variables(params(payload))
.build())
} catch (e: OptimizedQueryException) {
schema.execute(ExecutionInput
.newExecutionInput()
.query(query)
.variables(params(payload))
.build())
}
}
req.sendResponse(response)
}
Collections.singletonMap(cypher.variable, value)
} catch (e: Exception) {
e.printStackTrace()
}
}
server.start()

// CORS
Spark.before("/*") { req, res ->
res.header("Access-Control-Allow-Origin", "*")
res.header("Access-Control-Allow-Headers", "*")
res.type("application/json")
}

fun handler(req: Request, @Suppress("UNUSED_PARAMETER") res: Response) = req.body().let { body ->
val payload = parseBody(body)
val query = query(payload)
if (query.contains("__schema"))
schema.execute(query).let { println(render(it));it }
else run(translate(query, params(payload)).first())
}
}

Spark.options("/graphql") { _, _ -> "OK" }
Spark.post("/graphql", "application/json", ::handler, ::render)
private fun HttpExchange.sendResponse(data: Any?) {
val responseString = data?.let { mapper.writeValueAsString(it) }
// CORS
this.responseHeaders.add("Access-Control-Allow-Origin", "*")
this.responseHeaders.add("Access-Control-Allow-Headers", "*")
this.responseHeaders.add("Content-Type", "application/json")
this.sendResponseHeaders(200, responseString?.length?.toLong() ?: 0)
if (responseString != null) this.responseBody.use { it.write(responseString.toByteArray()) }
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.neo4j.graphql.utils

import com.fasterxml.jackson.databind.ObjectMapper
import com.intellij.rt.execution.junit.FileComparisonFailure
import org.codehaus.jackson.map.ObjectMapper
import org.junit.jupiter.api.DynamicContainer
import org.junit.jupiter.api.DynamicNode
import org.junit.jupiter.api.DynamicTest
Expand Down Expand Up @@ -269,7 +269,7 @@ open class AsciiDocTestSuite(
}
}

private fun fixNumber(v: Any?): Any? = when (v) {
fun fixNumber(v: Any?): Any? = when (v) {
is Float -> v.toDouble()
is Int -> v.toLong()
is Iterable<*> -> v.map { fixNumber(it) }
Expand Down
Loading