@@ -16,21 +16,30 @@ open class ProjectionBase {
1616 }
1717
1818 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 >> {
1927 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+ }
2636 }
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()
3443 }
3544
3645 fun where (variable : String , fieldDefinition : GraphQLFieldDefinition , type : GraphQLFieldsContainer , arguments : List <Argument >, field : Field ): Cypher {
@@ -139,9 +148,8 @@ open class ProjectionBase {
139148 return predicates.values + defaults
140149 }
141150
142- fun projectFields (variable : String , field : Field , nodeType : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? ): Cypher {
143- val queries = projectSelectionSet(variable, field.selectionSet, nodeType, env, variableSuffix)
144-
151+ fun projectFields (variable : String , field : Field , nodeType : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? , propertiesToSkipDeepProjection : Set <String > = emptySet()): Cypher {
152+ val queries = projectSelection(variable, field.selectionSet.selections, nodeType, env, variableSuffix, propertiesToSkipDeepProjection)
145153 @Suppress(" SimplifiableCallChain" )
146154 val projection = queries
147155 .map { it.query }
@@ -152,18 +160,18 @@ open class ProjectionBase {
152160 return Cypher (" $variable $projection " , params)
153161 }
154162
155- private fun projectSelectionSet (variable : String , selectionSet : SelectionSet , nodeType : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? ): List <Cypher > {
163+ private fun projectSelection (variable : String , selection : List < Selection < * >> , nodeType : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? , propertiesToSkipDeepProjection : Set < String > = emptySet() ): List <Cypher > {
156164 // TODO just render fragments on valid types (Labels) by using cypher like this:
157165 // apoc.map.mergeList([
158166 // a{.name},
159167 // CASE WHEN a:Location THEN a { .foo } ELSE {} END
160168 // ])
161169 var hasTypeName = false
162- val projections = selectionSet.selections .flatMapTo(mutableListOf<Cypher >()) {
170+ val projections = selection .flatMapTo(mutableListOf<Cypher >()) {
163171 when (it) {
164172 is Field -> {
165173 hasTypeName = hasTypeName || (it.name == TYPE_NAME )
166- listOf (projectField(variable, it, nodeType, env, variableSuffix))
174+ listOf (projectField(variable, it, nodeType, env, variableSuffix, propertiesToSkipDeepProjection ))
167175 }
168176 is InlineFragment -> projectInlineFragment(variable, it, env, variableSuffix)
169177 is FragmentSpread -> projectNamedFragments(variable, it, env, variableSuffix)
@@ -180,7 +188,7 @@ open class ProjectionBase {
180188 return projections
181189 }
182190
183- private fun projectField (variable : String , field : Field , type : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? ): Cypher {
191+ private fun projectField (variable : String , field : Field , type : GraphQLFieldsContainer , env : DataFetchingEnvironment , variableSuffix : String? , propertiesToSkipDeepProjection : Set < String > = emptySet() ): Cypher {
184192 if (field.name == TYPE_NAME ) {
185193 return if (type.isRelationType()) {
186194 Cypher (" ${field.aliasOrName()} : '${type.name} '" )
@@ -206,7 +214,14 @@ open class ProjectionBase {
206214 } ? : when {
207215 isObjectField -> {
208216 val patternComprehensions = if (fieldDefinition.isNeo4jType()) {
209- projectNeo4jObjectType(variable, field)
217+ if (propertiesToSkipDeepProjection.contains(fieldDefinition.innerName())) {
218+ // if the property has an internal type like Date or DateTime and we want to compute on this
219+ // type (e.g sorting), we need to pass out the whole property and do the concrete projection
220+ // after the outer computation is done
221+ Cypher (variable + " ." + fieldDefinition.propertyName().quote())
222+ } else {
223+ projectNeo4jObjectType(variable, field)
224+ }
210225 } else {
211226 projectRelationship(variable, field, fieldDefinition, type, env, variableSuffix)
212227 }
@@ -230,7 +245,7 @@ open class ProjectionBase {
230245 .filterIsInstance<Field >()
231246 .map {
232247 val value = when (it.name) {
233- NEO4j_FORMATTED_PROPERTY_KEY -> " $variable .${field.name} "
248+ NEO4j_FORMATTED_PROPERTY_KEY -> " toString( $variable .${field.name} ) "
234249 else -> " $variable .${field.name} .${it.name} "
235250 }
236251 " ${it.name} : $value "
@@ -266,7 +281,7 @@ open class ProjectionBase {
266281 val fragmentType = env.graphQLSchema.getType(fragmentTypeName) as ? GraphQLFieldsContainer ? : return emptyList()
267282 // these are the nested fields of the fragment
268283 // it could be that we have to adapt the variable name too, and perhaps add some kind of rename
269- return projectSelectionSet (variable, selectionSet, fragmentType, env, variableSuffix)
284+ return projectSelection (variable, selectionSet.selections , fragmentType, env, variableSuffix)
270285 }
271286
272287
@@ -336,9 +351,27 @@ open class ProjectionBase {
336351 val relPattern = if (isRelFromType) " $childVariable :${relInfo.relType} " else " :${relInfo.relType} "
337352
338353 val where = where(childVariable, fieldDefinition, nodeType, propertyArguments(field), field)
339- val fieldProjection = projectFields(childVariable, field, nodeType, env, variableSuffix)
340354
341- val comprehension = " [($variable )$inArrow -[$relPattern ]-$outArrow ($endNodePattern )${where.query} | ${fieldProjection.query} ]"
355+ val orderBy = getOrderByArgs(field.arguments)
356+ val sortByNeo4jTypeFields = orderBy
357+ .filter { (property, _) -> nodeType.getFieldDefinition(property)?.isNeo4jType() == true }
358+ .map { (property, _) -> property }
359+ .toSet()
360+
361+ val fieldProjection = projectFields(childVariable, field, nodeType, env, variableSuffix, sortByNeo4jTypeFields)
362+ var comprehension = " [($variable )$inArrow -[$relPattern ]-$outArrow ($endNodePattern )${where.query} | ${fieldProjection.query} ]"
363+ if (orderBy.isNotEmpty()) {
364+ val sortArgs = orderBy.joinToString(" , " , transform = { (property, direction) -> if (direction == Sort .ASC ) " '^$property '" else " '$property '" })
365+ comprehension = " apoc.coll.sortMulti($comprehension , [$sortArgs ])"
366+ if (sortByNeo4jTypeFields.isNotEmpty()) {
367+ val neo4jFieldSelection = field.selectionSet.selections
368+ .filter { selection -> sortByNeo4jTypeFields.contains((selection as ? Field )?.name) }
369+ val deferredProjection = projectSelection(" sortedElement" , neo4jFieldSelection, nodeType, env, variableSuffix)
370+ .map { cypher -> cypher.query }
371+ .joinNonEmpty(" , " )
372+ comprehension = " [sortedElement IN $comprehension | sortedElement { .*, $deferredProjection }]"
373+ }
374+ }
342375 val skipLimit = SkipLimit (childVariable, field.arguments)
343376 val slice = skipLimit.slice(fieldType.isList())
344377 return Cypher (comprehension + slice.query, (where.params + fieldProjection.params + slice.params))
@@ -392,4 +425,9 @@ open class ProjectionBase {
392425 }
393426 }
394427 }
428+
429+ enum class Sort {
430+ ASC ,
431+ DESC
432+ }
395433}
0 commit comments