@@ -16,21 +16,30 @@ open class ProjectionBase {
16
16
}
17
17
18
18
fun orderBy (variable : String , args : MutableList <Argument >): String {
19
+ val values = getOrderByArgs(args)
20
+ if (values.isEmpty()) {
21
+ return " "
22
+ }
23
+ return " ORDER BY " + values.joinToString(" , " , transform = { (property, direction) -> " $variable .$property $direction " })
24
+ }
25
+
26
+ private fun getOrderByArgs (args : MutableList <Argument >): List <Pair <String , Sort >> {
19
27
val arg = args.find { it.name == ORDER_BY }
20
- val values = arg?.value?.let { it ->
21
- when (it) {
22
- is ArrayValue -> it.values.map { it.toJavaValue().toString() }
23
- is EnumValue -> listOf (it.name)
24
- is StringValue -> listOf (it.value)
25
- else -> null
28
+ return arg?.value
29
+ ?.let { it ->
30
+ when (it) {
31
+ is ArrayValue -> it.values.map { it.toJavaValue().toString() }
32
+ is EnumValue -> listOf (it.name)
33
+ is StringValue -> listOf (it.value)
34
+ else -> null
35
+ }
26
36
}
27
- }
28
- @Suppress(" SimplifiableCallChain" )
29
- return if (values == null ) " "
30
- else " ORDER BY " + values
31
- .map { it.split(" _" ) }
32
- .map { " $variable .${it[0 ]} ${it[1 ].toUpperCase()} " }
33
- .joinToString(" , " )
37
+ ?.map {
38
+ val index = it.lastIndexOf(' _' )
39
+ val property = it.substring(0 , index)
40
+ val direction = Sort .valueOf(it.substring(index + 1 ).toUpperCase())
41
+ property to direction
42
+ } ? : emptyList()
34
43
}
35
44
36
45
fun where (variable : String , fieldDefinition : GraphQLFieldDefinition , type : GraphQLFieldsContainer , arguments : List <Argument >, field : Field ): Cypher {
@@ -133,8 +142,8 @@ open class ProjectionBase {
133
142
return predicates.values + defaults
134
143
}
135
144
136
- fun projectFields (variable : String , field : Field , nodeType : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? ): Cypher {
137
- val queries = projectSelectionSet (variable, field.selectionSet, nodeType, env, variableSuffix)
145
+ fun projectFields (variable : String , field : Field , nodeType : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? , neo4jFieldsToPass : Set < String > = emptySet() ): Cypher {
146
+ val queries = projectSelection (variable, field.selectionSet.selections , nodeType, env, variableSuffix, neo4jFieldsToPass )
138
147
@Suppress(" SimplifiableCallChain" )
139
148
val projection = queries
140
149
.map { it.query }
@@ -145,18 +154,18 @@ open class ProjectionBase {
145
154
return Cypher (" $variable $projection " , params)
146
155
}
147
156
148
- private fun projectSelectionSet (variable : String , selectionSet : SelectionSet , nodeType : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? ): List <Cypher > {
157
+ private fun projectSelection (variable : String , selection : List < Selection < * >> , nodeType : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? , neo4jFieldsToPass : Set < String > = emptySet() ): List <Cypher > {
149
158
// TODO just render fragments on valid types (Labels) by using cypher like this:
150
159
// apoc.map.mergeList([
151
160
// a{.name},
152
161
// CASE WHEN a:Location THEN a { .foo } ELSE {} END
153
162
// ])
154
163
var hasTypeName = false
155
- val projections = selectionSet.selections .flatMapTo(mutableListOf<Cypher >()) {
164
+ val projections = selection .flatMapTo(mutableListOf<Cypher >()) {
156
165
when (it) {
157
166
is Field -> {
158
167
hasTypeName = hasTypeName || (it.name == TYPE_NAME )
159
- listOf (projectField(variable, it, nodeType, env, variableSuffix))
168
+ listOf (projectField(variable, it, nodeType, env, variableSuffix, neo4jFieldsToPass ))
160
169
}
161
170
is InlineFragment -> projectInlineFragment(variable, it, env, variableSuffix)
162
171
is FragmentSpread -> projectNamedFragments(variable, it, env, variableSuffix)
@@ -173,7 +182,7 @@ open class ProjectionBase {
173
182
return projections
174
183
}
175
184
176
- private fun projectField (variable : String , field : Field , type : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? ): Cypher {
185
+ private fun projectField (variable : String , field : Field , type : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? , neo4jFieldsToPass : Set < String > = emptySet() ): Cypher {
177
186
if (field.name == TYPE_NAME ) {
178
187
return if (type.isRelationType()) {
179
188
Cypher (" ${field.aliasOrName()} : '${type.name} '" )
@@ -199,7 +208,11 @@ open class ProjectionBase {
199
208
} ? : when {
200
209
isObjectField -> {
201
210
val patternComprehensions = if (fieldDefinition.isNeo4jType()) {
202
- projectNeo4jObjectType(variable, field)
211
+ if (neo4jFieldsToPass.contains(fieldDefinition.innerName())) {
212
+ Cypher (variable + " ." + fieldDefinition.propertyName().quote())
213
+ } else {
214
+ projectNeo4jObjectType(variable, field)
215
+ }
203
216
} else {
204
217
projectRelationship(variable, field, fieldDefinition, type, env, variableSuffix)
205
218
}
@@ -223,7 +236,7 @@ open class ProjectionBase {
223
236
.filterIsInstance<Field >()
224
237
.map {
225
238
val value = when (it.name) {
226
- NEO4j_FORMATTED_PROPERTY_KEY -> " $variable .${field.name} "
239
+ NEO4j_FORMATTED_PROPERTY_KEY -> " toString( $variable .${field.name} ) "
227
240
else -> " $variable .${field.name} .${it.name} "
228
241
}
229
242
" ${it.name} : $value "
@@ -259,7 +272,7 @@ open class ProjectionBase {
259
272
val fragmentType = env.graphQLSchema.getType(fragmentTypeName) as ? GraphQLFieldsContainer ? : return emptyList()
260
273
// these are the nested fields of the fragment
261
274
// it could be that we have to adapt the variable name too, and perhaps add some kind of rename
262
- return projectSelectionSet (variable, selectionSet, fragmentType, env, variableSuffix)
275
+ return projectSelection (variable, selectionSet.selections , fragmentType, env, variableSuffix)
263
276
}
264
277
265
278
@@ -329,9 +342,27 @@ open class ProjectionBase {
329
342
val relPattern = if (isRelFromType) " $childVariable :${relInfo.relType} " else " :${relInfo.relType} "
330
343
331
344
val where = where(childVariable, fieldDefinition, nodeType, propertyArguments(field), field)
332
- val fieldProjection = projectFields(childVariable, field, nodeType, env, variableSuffix)
333
345
334
- val comprehension = " [($variable )$inArrow -[$relPattern ]-$outArrow ($endNodePattern )${where.query} | ${fieldProjection.query} ]"
346
+ val orderBy = getOrderByArgs(field.arguments)
347
+ val sortByNeo4jTypeFields = orderBy
348
+ .filter { (property, _) -> nodeType.getFieldDefinition(property)?.isNeo4jType() == true }
349
+ .map { (property, _) -> property }
350
+ .toSet()
351
+
352
+ val fieldProjection = projectFields(childVariable, field, nodeType, env, variableSuffix, sortByNeo4jTypeFields)
353
+ var comprehension = " [($variable )$inArrow -[$relPattern ]-$outArrow ($endNodePattern )${where.query} | ${fieldProjection.query} ]"
354
+ if (orderBy.isNotEmpty()) {
355
+ val sortArgs = orderBy.joinToString(" , " , transform = { (property, direction) -> if (direction == Sort .ASC ) " '^$property '" else " '$property '" })
356
+ comprehension = " apoc.coll.sortMulti($comprehension , [$sortArgs ])"
357
+ if (sortByNeo4jTypeFields.isNotEmpty()) {
358
+ val neo4jFiledSelection = field.selectionSet.selections
359
+ .filter { selection -> sortByNeo4jTypeFields.contains((selection as ? Field )?.name) }
360
+ val deferredProjection = projectSelection(" sortedElement" , neo4jFiledSelection, nodeType, env, variableSuffix)
361
+ .map { cypher -> cypher.query }
362
+ .joinNonEmpty(" , " )
363
+ comprehension = " [sortedElement IN $comprehension | sortedElement { .*, $deferredProjection }]"
364
+ }
365
+ }
335
366
val skipLimit = SkipLimit (childVariable, field.arguments)
336
367
val slice = skipLimit.slice(fieldType.isList())
337
368
return Cypher (comprehension + slice.query, (where.params + fieldProjection.params + slice.params))
@@ -388,4 +419,9 @@ open class ProjectionBase {
388
419
}
389
420
}
390
421
}
391
- }
422
+
423
+ enum class Sort {
424
+ ASC ,
425
+ DESC
426
+ }
427
+ }
0 commit comments