Skip to content

Commit e3422ff

Browse files
committed
Make use of sub-queries, to get rid of most apoc calls.
resolves #214
1 parent e3c118e commit e3422ff

32 files changed

+1171
-555
lines changed

core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
<dependency>
5959
<groupId>org.neo4j</groupId>
6060
<artifactId>neo4j-cypher-dsl</artifactId>
61-
<version>2021.0.2</version>
61+
<version>2021.2.1</version>
6262
</dependency>
6363
<dependency>
6464
<groupId>ch.qos.logback</groupId>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.neo4j.graphql
33
import graphql.language.VariableReference
44
import graphql.schema.GraphQLArgument
55
import graphql.schema.GraphQLInputType
6+
import graphql.schema.GraphQLOutputType
67
import graphql.schema.GraphQLType
78
import org.neo4j.cypherdsl.core.*
89

@@ -26,6 +27,9 @@ fun queryParameter(value: Any?, vararg parts: String?): Parameter<Any> {
2627
return org.neo4j.cypherdsl.core.Cypher.parameter(name).withValue(value?.toJavaValue())
2728
}
2829

30+
fun Expression.collect(type: GraphQLOutputType) = if (type.isList()) Functions.collect(this) else this
31+
fun StatementBuilder.OngoingReading.withSubQueries(subQueries: List<Statement>) = subQueries.fold(this, { it, sub -> it.call(sub) })
32+
2933
fun normalizeName(vararg parts: String?) = parts.mapNotNull { it?.capitalize() }.filter { it.isNotBlank() }.joinToString("").decapitalize()
3034
//fun normalizeName(vararg parts: String?) = parts.filterNot { it.isNullOrBlank() }.joinToString("_")
3135

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_FROM
1818
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_NAME
1919
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_TO
2020
import org.neo4j.graphql.handler.projection.ProjectionBase
21+
import org.slf4j.LoggerFactory
2122
import java.math.BigDecimal
2223
import java.math.BigInteger
2324

@@ -201,10 +202,19 @@ fun <T> GraphQLDirective.getArgument(argumentName: String, defaultValue: T? = nu
201202
?: throw IllegalStateException("No default value for @${this.name}::$argumentName")
202203
}
203204

204-
fun GraphQLFieldDefinition.cypherDirective() :CypherDirective?= getDirective(CYPHER)?.let { CypherDirective(
205-
it.getMandatoryArgument(CYPHER_STATEMENT),
206-
it.getMandatoryArgument(CYPHER_PASS_THROUGH, false)
207-
) }
205+
private val LOGGER = LoggerFactory.getLogger(CypherDirective::class.java)
206+
207+
208+
fun GraphQLFieldDefinition.cypherDirective(): CypherDirective? = getDirective(CYPHER)?.let {
209+
val originalStatement = it.getMandatoryArgument<String>(CYPHER_STATEMENT)
210+
val rewrittenStatement = originalStatement.replace(Regex("\\\$([_a-zA-Z]\\w*)"), "$1")
211+
if (originalStatement != rewrittenStatement) {
212+
LoggerFactory.getLogger(CypherDirective::class.java)
213+
.warn("The field arguments used in the directives statement must not contain parameters. The statement was replaced. Please adjust your GraphQl Schema.\n\tGot : {}\n\tReplaced by: {}\n\tField : {} ({})",
214+
originalStatement, rewrittenStatement, this.name, this.definition?.sourceLocation)
215+
}
216+
CypherDirective(rewrittenStatement, it.getMandatoryArgument(CYPHER_PASS_THROUGH, false))
217+
}
208218

209219
data class CypherDirective(val statement: String, val passThrough: Boolean)
210220

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ import graphql.language.NullValue
55
import graphql.language.ObjectValue
66
import graphql.language.Value
77
import graphql.schema.*
8-
import org.neo4j.cypherdsl.core.*
8+
import org.neo4j.cypherdsl.core.Condition
9+
import org.neo4j.cypherdsl.core.Expression
10+
import org.neo4j.cypherdsl.core.Property
11+
import org.neo4j.cypherdsl.core.PropertyContainer
912
import org.slf4j.LoggerFactory
1013

1114
typealias CypherDSL = org.neo4j.cypherdsl.core.Cypher
1215

1316
enum class FieldOperator(
1417
val suffix: String,
15-
val op: String,
18+
op: String,
1619
private val conditionCreator: (Expression, Expression) -> Condition,
1720
val not: Boolean = false,
1821
val requireParam: Boolean = true,
@@ -126,17 +129,17 @@ enum class FieldOperator(
126129
fun fieldName(fieldName: String) = fieldName + suffix
127130
}
128131

129-
enum class RelationOperator(val suffix: String, val op: String) {
130-
SOME("_some", "ANY"),
132+
enum class RelationOperator(val suffix: String) {
133+
SOME("_some"),
131134

132-
EVERY("_every", "ALL"),
135+
EVERY("_every"),
133136

134-
SINGLE("_single", "SINGLE"),
135-
NONE("_none", "NONE"),
137+
SINGLE("_single"),
138+
NONE("_none"),
136139

137140
// `eq` if queried with an object, `not exists` if queried with null
138-
EQ_OR_NOT_EXISTS("", ""),
139-
NOT("_not", "");
141+
EQ_OR_NOT_EXISTS(""),
142+
NOT("_not");
140143

141144
fun fieldName(fieldName: String) = fieldName + suffix
142145

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package org.neo4j.graphql.handler
33
import graphql.language.Field
44
import graphql.schema.*
55
import org.neo4j.cypherdsl.core.Statement
6-
import org.neo4j.cypherdsl.core.StatementBuilder
76
import org.neo4j.graphql.*
87

98
/**
@@ -41,8 +40,8 @@ class CreateTypeHandler private constructor(
4140
if (!canHandle(type)) {
4241
return null
4342
}
44-
return when {
45-
fieldDefinition.name == "create${type.name}" -> CreateTypeHandler(type, fieldDefinition, schemaConfig)
43+
return when (fieldDefinition.name) {
44+
"create${type.name}" -> CreateTypeHandler(type, fieldDefinition, schemaConfig)
4645
else -> null
4746
}
4847
}
@@ -82,11 +81,11 @@ class CreateTypeHandler private constructor(
8281
val node = org.neo4j.cypherdsl.core.Cypher.node(type.name, *additionalTypes.toTypedArray()).named(variable)
8382

8483
val properties = properties(variable, field.arguments)
85-
val mapProjection = projectFields(node, field, type, env)
84+
val (mapProjection, subQueries) = projectFields(node, field, type, env)
8685

87-
val update: StatementBuilder.OngoingUpdate = org.neo4j.cypherdsl.core.Cypher.create(node.withProperties(*properties))
88-
return update
86+
return org.neo4j.cypherdsl.core.Cypher.create(node.withProperties(*properties))
8987
.with(node)
88+
.withSubQueries(subQueries)
9089
.returning(node.project(mapProjection).`as`(field.aliasOrName()))
9190
.build()
9291
}

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

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import graphql.schema.DataFetcher
55
import graphql.schema.DataFetchingEnvironment
66
import graphql.schema.GraphQLFieldDefinition
77
import graphql.schema.GraphQLFieldsContainer
8-
import org.neo4j.cypherdsl.core.Functions
98
import org.neo4j.cypherdsl.core.Statement
109
import org.neo4j.graphql.*
1110

@@ -14,7 +13,6 @@ import org.neo4j.graphql.*
1413
*/
1514
class CypherDirectiveHandler(
1615
private val type: GraphQLFieldsContainer?,
17-
private val isQuery: Boolean,
1816
private val cypherDirective: CypherDirective,
1917
fieldDefinition: GraphQLFieldDefinition,
2018
schemaConfig: SchemaConfig)
@@ -25,38 +23,26 @@ class CypherDirectiveHandler(
2523
override fun createDataFetcher(operationType: OperationType, fieldDefinition: GraphQLFieldDefinition): DataFetcher<Cypher>? {
2624
val cypherDirective = fieldDefinition.cypherDirective() ?: return null
2725
val type = fieldDefinition.type.inner() as? GraphQLFieldsContainer
28-
val isQuery = operationType == OperationType.QUERY
29-
return CypherDirectiveHandler(type, isQuery, cypherDirective, fieldDefinition, schemaConfig)
26+
return CypherDirectiveHandler(type, cypherDirective, fieldDefinition, schemaConfig)
3027
}
3128
}
3229

3330
override fun generateCypher(variable: String, field: Field, env: DataFetchingEnvironment): Statement {
34-
35-
val query = if (isQuery) {
36-
val nestedQuery = cypherDirective(variable, fieldDefinition, field, cypherDirective)
37-
org.neo4j.cypherdsl.core.Cypher.unwind(nestedQuery).`as`(variable)
38-
} else {
39-
val args = cypherDirectiveQuery(variable, fieldDefinition, field, cypherDirective)
40-
41-
val value = org.neo4j.cypherdsl.core.Cypher.name("value")
42-
org.neo4j.cypherdsl.core.Cypher.call("apoc.cypher.doIt")
43-
.withArgs(*args)
44-
.yield(value)
45-
.with(org.neo4j.cypherdsl.core.Cypher.property(value, Functions.head(org.neo4j.cypherdsl.core.Cypher.call("keys").withArgs(value).asFunction())).`as`(variable))
46-
}
4731
val node = org.neo4j.cypherdsl.core.Cypher.anyNode(variable)
48-
val readingWithWhere = if (type != null && !cypherDirective.passThrough) {
49-
val projectionEntries = projectFields(node, field, type, env)
50-
query.returning(node.project(projectionEntries).`as`(field.aliasOrName()))
51-
} else {
52-
query.returning(node.`as`(field.aliasOrName()))
53-
}
54-
val ordering = orderBy(node, field.arguments, fieldDefinition, env.variables)
55-
val skipLimit = SkipLimit(variable, field.arguments, fieldDefinition)
56-
57-
val resultWithSkipLimit = readingWithWhere
58-
.let { if (ordering != null) skipLimit.format(it.orderBy(*ordering.toTypedArray())) else skipLimit.format(it) }
59-
60-
return resultWithSkipLimit.build()
32+
val ctxVariable = node.requiredSymbolicName
33+
val nestedQuery = cypherDirective(ctxVariable, fieldDefinition, field, cypherDirective, null, env)
34+
35+
return org.neo4j.cypherdsl.core.Cypher.call(nestedQuery)
36+
.let { reading ->
37+
if (type == null || cypherDirective.passThrough) {
38+
reading.returning(ctxVariable.`as`(field.aliasOrName()))
39+
} else {
40+
val (fieldProjection, nestedSubQueries) = projectFields(node, field, type, env)
41+
reading
42+
.withSubQueries(nestedSubQueries)
43+
.returning(ctxVariable.project(fieldProjection).`as`(field.aliasOrName()))
44+
}
45+
}
46+
.build()
6147
}
6248
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import graphql.schema.*
55
import org.neo4j.cypherdsl.core.Node
66
import org.neo4j.cypherdsl.core.Relationship
77
import org.neo4j.cypherdsl.core.Statement
8-
import org.neo4j.cypherdsl.core.StatementBuilder.OngoingUpdate
98
import org.neo4j.graphql.*
109

1110
/**
@@ -79,12 +78,13 @@ class DeleteHandler private constructor(
7978
.where(where)
8079
}
8180
val deletedElement = propertyContainer.requiredSymbolicName.`as`("toDelete")
82-
val mapProjection = projectFields(propertyContainer, field, type, env)
81+
val (mapProjection, subQueries) = projectFields(propertyContainer, field, type, env)
8382

8483
val projection = propertyContainer.project(mapProjection).`as`(variable)
85-
val update: OngoingUpdate = select.with(deletedElement, projection)
84+
return select
85+
.withSubQueries(subQueries)
86+
.with(deletedElement, projection)
8687
.detachDelete(deletedElement)
87-
return update
8888
.returning(projection.asName().`as`(field.aliasOrName()))
8989
.build()
9090
}

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import graphql.schema.GraphQLFieldsContainer
88
import org.neo4j.cypherdsl.core.Node
99
import org.neo4j.cypherdsl.core.Relationship
1010
import org.neo4j.cypherdsl.core.Statement
11-
import org.neo4j.cypherdsl.core.StatementBuilder.OngoingMatchAndUpdate
1211
import org.neo4j.graphql.*
1312

1413
/**
@@ -108,12 +107,12 @@ class MergeOrUpdateHandler private constructor(
108107
}
109108
}
110109
val properties = properties(variable, field.arguments)
111-
val mapProjection = projectFields(propertyContainer, field, type, env)
112-
val update: OngoingMatchAndUpdate = select
113-
.mutate(propertyContainer, org.neo4j.cypherdsl.core.Cypher.mapOf(*properties))
110+
val (mapProjection, subQueries) = projectFields(propertyContainer, field, type, env)
114111

115-
return update
112+
return select
113+
.mutate(propertyContainer, org.neo4j.cypherdsl.core.Cypher.mapOf(*properties))
116114
.with(propertyContainer)
115+
.withSubQueries(subQueries)
117116
.returning(propertyContainer.project(mapProjection).`as`(field.aliasOrName()))
118117
.build()
119118
}

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,12 @@ class QueryHandler private constructor(
119119
match.where(where)
120120
}
121121

122-
val ordering = orderBy(propertyContainer, field.arguments, fieldDefinition, env.variables)
123-
val skipLimit = SkipLimit(variable, field.arguments, fieldDefinition)
124-
125-
val projectionEntries = projectFields(propertyContainer, field, type, env)
122+
val (projectionEntries, subQueries) = projectFields(propertyContainer, field, type, env)
126123
val mapProjection = propertyContainer.project(projectionEntries).`as`(field.aliasOrName())
127-
val resultWithSkipLimit = ongoingReading.returning(mapProjection)
128-
.let {
129-
val orderedResult = ordering?.let { o -> it.orderBy(*o.toTypedArray()) } ?: it
130-
skipLimit.format(orderedResult)
131-
}
132-
133-
return resultWithSkipLimit.build()
124+
return ongoingReading
125+
.withSubQueries(subQueries)
126+
.returning(mapProjection)
127+
.skipLimitOrder(propertyContainer.requiredSymbolicName, fieldDefinition, field, env)
128+
.build()
134129
}
135130
}

0 commit comments

Comments
 (0)