Skip to content

Commit 87efbce

Browse files
committed
Prepare for first milestone release
- Kotlin Update to 1.3.11 - set version to 1.0.0-M01 - added more docs - added example server - updated readme
1 parent c955986 commit 87efbce

File tree

8 files changed

+287
-48
lines changed

8 files changed

+287
-48
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
.DS_Store
12
dependency-reduced-pom.xml
2-
docs
33
*.iml
44
target
5-
.idea
5+
.idea

docs/Server.groovy

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Simplistic GraphQL Server using SparkJava
2+
@Grapes([
3+
@Grab('com.sparkjava:spark-core:2.7.2'),
4+
@Grab('org.neo4j.driver:neo4j-java-driver:1.7.2'),
5+
@Grab('org.neo4j:neo4j-graphql-java:1.0.0-M01'),
6+
@Grab('com.google.code.gson:gson:2.8.5')
7+
])
8+
9+
import spark.*
10+
import static spark.Spark.*
11+
import com.google.gson.Gson
12+
import org.neo4j.graphql.*
13+
import org.neo4j.driver.v1.*
14+
15+
schema = """
16+
type Person {
17+
name: String
18+
born: Int
19+
actedIn: [Movie] @relation(name:"ACTED_IN")
20+
}
21+
type Movie {
22+
title: String
23+
released: Int
24+
tagline: String
25+
}
26+
type Query {
27+
person : [Person]
28+
}
29+
"""
30+
31+
gson = new Gson()
32+
render = (ResponseTransformer)gson.&toJson
33+
def query(value) { gson.fromJson(value,Map.class)["query"] }
34+
35+
graphql = new Translator(SchemaBuilder.buildSchema(schema))
36+
def translate(query) { graphql.translate(query) }
37+
38+
driver = GraphDatabase.driver("bolt://localhost",AuthTokens.basic("neo4j","password"))
39+
def run(cypher) { driver.session().withCloseable { it.run(cypher.query, Values.value(cypher.params)).list{ it.asMap() }}}
40+
41+
post("/graphql","application/json", { req, res -> run(translate(query(req.body())).first()) }, render);

docs/graphiql.jpg

107 KB
Loading

pom.xml

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
<groupId>org.neo4j</groupId>
88
<artifactId>neo4j-graphql-java</artifactId>
9+
<name>Neo4j GraphQL Java</name>
910
<description>GraphQL to Cypher Mapping</description>
10-
<version>1.0.0</version>
11+
<version>1.0.0-M01</version>
12+
<url>http://github.com/neo4j-contrib/neo4j-tinkerpop-api</url>
1113

1214
<licenses>
1315
<license>
@@ -20,7 +22,7 @@
2022
<properties>
2123
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2224
<java.version>1.8</java.version>
23-
<kotlin.version>1.2.61</kotlin.version>
25+
<kotlin.version>1.3.11</kotlin.version>
2426
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
2527
<neo4j.version>3.4.1</neo4j.version>
2628
<driver.version>1.6.2</driver.version>
@@ -63,18 +65,13 @@
6365
<groupId>org.neo4j</groupId>
6466
<artifactId>server-api</artifactId>
6567
<version>${neo4j.version}</version>
66-
<scope>provided</scope>
67-
</dependency>
68-
<dependency>
69-
<groupId>org.antlr</groupId>
70-
<artifactId>antlr4-runtime</artifactId>
71-
<version>4.5.1</version>
68+
<scope>test</scope>
7269
</dependency>
7370
<dependency>
7471
<groupId>org.codehaus.jackson</groupId>
7572
<artifactId>jackson-mapper-asl</artifactId>
7673
<version>1.9.13</version>
77-
<scope>provided</scope>
74+
<scope>test</scope>
7875
</dependency>
7976
<dependency>
8077
<groupId>com.graphql-java</groupId>
@@ -100,34 +97,6 @@
10097
</dependency>
10198
</dependencies>
10299

103-
<repositories>
104-
<repository>
105-
<releases>
106-
<enabled>true</enabled>
107-
</releases>
108-
<snapshots>
109-
<enabled>false</enabled>
110-
</snapshots>
111-
<id>bintray.kotlin.eap</id>
112-
<name>Bintray Kotlin EAP Repository</name>
113-
<url>http://dl.bintray.com/kotlin/kotlin-eap</url>
114-
</repository>
115-
</repositories>
116-
117-
<pluginRepositories>
118-
<pluginRepository>
119-
<releases>
120-
<enabled>true</enabled>
121-
</releases>
122-
<snapshots>
123-
<enabled>false</enabled>
124-
</snapshots>
125-
<id>bintray.kotlin.eap</id>
126-
<name>Bintray Kotlin EAP Repository</name>
127-
<url>http://dl.bintray.com/kotlin/kotlin-eap</url>
128-
</pluginRepository>
129-
</pluginRepositories>
130-
131100
<build>
132101
<plugins>
133102
<plugin>

readme.adoc

Lines changed: 230 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
This is an early stage alpha implementation written in Kotlin.
44

5+
== How does it work
6+
7+
This library
8+
9+
1. parses a GraphQL schema and
10+
2. uses the information of the annotated schema to translate _GraphQL_ queries and parameters into _Cypher_ queries and parameters.
11+
12+
Those Cypher queries can then executed, e.g via the Neo4j-Java-Driver (or other JVM drivers) against the graph database and the results can be returned directly to the caller.
13+
14+
The request, result and error handling is not part of this library, but we provide demo programs on how to use it in different languages.
15+
16+
NOTE: All the supported features are listed below, detailed docs will be added.
17+
18+
== Usage
19+
20+
You can use the library as dependency: `org.neo4j:neo4j-graphql-java:1.0.0-M01` in any JVM program.
21+
522
The basic usage should be:
623

724
[source,kotlin]
@@ -22,11 +39,67 @@ val query = """ { p:personByName(name:"Joe") { age } } """
2239
val schema = SchemaBuilder.buildSchema(idl)
2340
val (cypher, params) = Translator(schema).translate(query, params)
2441
25-
cypher == "MATCH (p:Person) WHERE p.name = 'Joe' RETURN p {.age}"
42+
cypher == "MATCH (p:Person) WHERE p.name = $pName RETURN p {.age} as p"
43+
----
44+
45+
== Demo
46+
47+
Here is a minimalistic example in Groovy using the Neo4j-Java driver and Spark-Java as webserver.
48+
It is running against a Neo4j instance at `bolt://localhost` (username: `neo4j`, password: `password`) containing the `:play movies` graph.
49+
50+
[source,groovy]
51+
----
52+
include::docs/Server.groovy[]
2653
----
2754

55+
Run the example with:
56+
57+
----
58+
groovy docs/Server.groovy
59+
----
60+
61+
and use http://localhost:4567/graphql as your GraphQL URL.
62+
63+
It uses a schema of:
64+
65+
[source,graphql]
66+
----
67+
type Person {
68+
name: String
69+
born: Int
70+
actedIn: [Movie] @relation(name:"ACTED_IN")
71+
}
72+
type Movie {
73+
title: String
74+
released: Int
75+
tagline: String
76+
}
77+
type Query {
78+
person : [Person]
79+
}
80+
----
81+
82+
And can run queries like:
83+
84+
[source,graphql]
85+
----
86+
{
87+
person(first:3) {
88+
name
89+
born
90+
actedIn(first:2) {
91+
title
92+
}
93+
}
94+
}
95+
----
96+
97+
image::docs/graphiql.jpg[]
98+
2899
== Features
29100

101+
=== Current
102+
30103
* parse SDL schema
31104
* resolve query fields via result types
32105
* handle arguments as equality comparisons for top level and nested fields
@@ -37,11 +110,8 @@ cypher == "MATCH (p:Person) WHERE p.name = 'Joe' RETURN p {.age}"
37110
* parametrization
38111
* aliases
39112
* inline and named fragments
40-
* sorting (top-level)
41-
* @relationship types
42-
* filters
43113

44-
== Next
114+
=== Next
45115

46116
* sorting (nested)
47117
* interfaces
@@ -50,3 +120,158 @@ cypher == "MATCH (p:Person) WHERE p.name = 'Joe' RETURN p {.age}"
50120
* auto-generate queries
51121
* auto-generate mutations
52122
* unions
123+
* scalars
124+
* date(time), spatial
125+
126+
== Documentation
127+
128+
=== Parse SDL schema
129+
130+
Currently schemas with object types, enums and Query types are parsed and handled.
131+
It supports the built-in scalars for GraphQL.
132+
133+
=== Resolve query Fields via Result Types
134+
135+
For _query fields_ that result in object types (even if wrapped in list/non-null), the appropriate object type is found in the schema and used to translate the query.
136+
137+
e.g.
138+
139+
[source,graphql]
140+
----
141+
type Query {
142+
person: [Person]
143+
}
144+
# query "person" is resolved to and via "Person"
145+
146+
type Person {
147+
name : String
148+
}
149+
----
150+
151+
=== Handle Arguments as Equality Comparisons for Top Level and Nested Fields
152+
153+
If you add a simple argument to your top-level query or nested related fields, those will be translated to direct equality comparisons.
154+
155+
[source,graphql]
156+
----
157+
person(name:"Joe", age:42) {
158+
name
159+
}
160+
----
161+
162+
to
163+
164+
[source,cypher]
165+
----
166+
MATCH (person:Person) WHERE person.name = 'Joe' AND person.age = 42 RETURN person { .name } AS person
167+
----
168+
169+
Only that the literal values are turned into parameters.
170+
171+
=== Handle Relationships via @relation Directive on Schema Fields
172+
173+
If you want to represent a relationship from the graph in GraphQL you have to add an `@relation` directive that contains the relationship-type and the direction.
174+
Default relationship-type is 'OUT'.
175+
So you can use different domain names in your GraphQL fields that are independent of your graph model.
176+
177+
[source,graphql]
178+
----
179+
type Person {
180+
name : String
181+
actedIn: [Movie]
182+
}
183+
----
184+
185+
----
186+
person(name:"Keanu Reeves") {
187+
name
188+
actedIn {
189+
title
190+
}
191+
}
192+
----
193+
194+
195+
=== Handle first, offset Arguments
196+
197+
To support pagination `first` is translated to `LIMIT` in Cypher and `offset` into `SKIP`
198+
For nested queries these are converted into slices for arrays.
199+
200+
=== Argument Types: string, int, float, array
201+
202+
The default Neo4j types are handled both as argument types as well as field types.
203+
204+
NOTE: Datetime and spatial not yet.
205+
206+
=== Parameter Support
207+
208+
We handle passed in GraphQL parameters, these are resolved correctly when used within the GraphQL query.
209+
210+
211+
=== Parametrization
212+
213+
As we don't want to have literal values in our Cypher queries, all of them are translated into parameters.
214+
215+
[source,graphql]
216+
----
217+
person(name:"Joe", age:42, first:10) {
218+
name
219+
}
220+
----
221+
222+
to
223+
224+
[source,cypher]
225+
----
226+
MATCH (person:Person) WHERE person.name = $personName AND person.age = $personAge RETURN person { .name } AS person LIMIT $first
227+
----
228+
229+
Those parameters are returned as part of the `Cypher` type that's returned from the `translate()` method.
230+
231+
=== Aliases
232+
233+
We support query aliases, they are used as Cypher aliases too, so you get them back as keys in your result records.
234+
235+
=== Inline and Named Fragments
236+
237+
This is more of a technical feature, both types of fragments are resolved internally.
238+
239+
=== Sorting (top-level)
240+
241+
We support sorting via an `orderBy` argument, which takes an Enum or String value of `fieldName_asc` or `fieldName_desc`.
242+
243+
NOTE: Those enums are not yet automatically generated.
244+
245+
=== @relationship on Types
246+
247+
To represent rich relationship types with properties, a @relation directive is supported on an object Type
248+
249+
250+
=== Filters
251+
252+
Filters are a powerful way of selecting a subset of data.
253+
Inspired by the https://www.graph.cool/docs/reference/graphql-api/query-api-nia9nushae[graph.cool/Prisma filter approach], our filters work the same way.
254+
255+
NOTE: we'll create more detailed docs, for now the prisma docs on that topic are pretty good.
256+
257+
258+
We use nested input types for arbitrary filtering on query types and fields
259+
260+
----
261+
{ Company(filter: { AND: { name_contains: "Ne", country_in ["SE"]}}) { name } }
262+
----
263+
264+
You can also apply nested filter on relations, which use suffixes like `("",not,some, none, single, every)`
265+
266+
----
267+
{ Company(filter: {
268+
employees_none { name_contains: "Jan"},
269+
employees_some: { gender_in : [female]},
270+
company_not: null })
271+
{
272+
name
273+
}
274+
}
275+
----
276+
277+
NOTE: Those nested input types are not yet generated, we use leniency in the parser.

0 commit comments

Comments
 (0)