@@ -10,39 +10,37 @@ import graphql.schema.idl.SchemaParser
1010import org.antlr.v4.runtime.misc.ParseCancellationException
1111
1212class Translator (val schema : GraphQLSchema ) {
13- companion object {
14- val EMPTY_RESULT = " " to emptyMap<String ,Any >()
15- }
16- data class Config ( val topLevelWhere : Boolean = true )
13+ data class Context (val topLevelWhere : Boolean = true , val fragments : Map <String ,FragmentDefinition > = emptyMap())
1714 data class Cypher ( val query : String , val params : Map <String ,Any ?> = emptyMap()) {
1815 companion object {
1916 val EMPTY = Cypher (" " )
2017 }
2118 fun with (p : Map <String ,Any ?>) = this .copy(params = this .params + p)
2219 }
2320
24- fun translate (query : String , params : Map <String , Any > = emptyMap(), config : Config = Config ()) : List <Cypher > {
21+ fun translate (query : String , params : Map <String , Any > = emptyMap(), context : Context = Context ()) : List <Cypher > {
2522 val ast = parse(query) // todo preparsedDocumentProvider
23+ val ctx = context.copy(fragments = ast.definitions.filterIsInstance<FragmentDefinition >().map { it.name to it }.toMap())
2624 val queries = ast.definitions.filterIsInstance<OperationDefinition >()
2725 .filter { it.operation == OperationDefinition .Operation .QUERY } // todo variabledefinitions, directives, name
2826 .flatMap { it.selectionSet.selections }
2927 .filterIsInstance<Field >() // FragmentSpread, InlineFragment
3028 .map { println (it);it }
31- .map { toQuery(it, config ).with (params) } // arguments, alias, directives, selectionSet
29+ .map { toQuery(it, ctx ).with (params) } // arguments, alias, directives, selectionSet
3230 return queries
3331 }
3432
35- private fun toQuery (queryField : Field , config : Config = Config ()): Cypher {
33+ private fun toQuery (queryField : Field , ctx : Context = Context ()): Cypher {
3634 val name = queryField.name
3735 val queryType = schema.queryType.fieldDefinitions.filter { it.name == name }.firstOrNull() ? : throw IllegalArgumentException (" Unknown Query $name available queries: " + schema.queryType.fieldDefinitions.map { it.name }.joinToString())
3836 val returnType = inner(queryType.type)
3937// println(returnType)
4038 val type = schema.getType(returnType.name)
4139 val label = type.name.quote()
4240 val variable = queryField.aliasOrName().decapitalize()
43- val mapProjection = projectFields(variable, queryField, type)
44- val where = if (config .topLevelWhere) where(variable, queryType, propertyArguments(queryField)) else Cypher .EMPTY
45- val properties = if (config .topLevelWhere) Cypher .EMPTY else properties(variable, queryType, propertyArguments(queryField))
41+ val mapProjection = projectFields(variable, queryField, type, ctx )
42+ val where = if (ctx .topLevelWhere) where(variable, queryType, propertyArguments(queryField)) else Cypher .EMPTY
43+ val properties = if (ctx .topLevelWhere) Cypher .EMPTY else properties(variable, queryType, propertyArguments(queryField))
4644 val skipLimit = format(skipLimit(queryField.arguments))
4745 val ordering = orderBy(variable, queryField.arguments)
4846 return Cypher (" MATCH ($variable :$label${properties.query} )${where.query} RETURN ${mapProjection.query} AS $variable$ordering$skipLimit " ,
@@ -86,27 +84,52 @@ class Translator(val schema: GraphQLSchema) {
8684 return predicates + defaults
8785 }
8886
89- private fun projectFields (variable : String , field : Field , type : GraphQLType ): Cypher {
87+ private fun projectFields (variable : String , field : Field , type : GraphQLType , ctx : Context ): Cypher {
9088 // todo handle non-object case
9189 val objectType = type as GraphQLObjectType
92- val properties = field.selectionSet.selections
93- .filterIsInstance<Field >()
94- .map { resolveField(variable, it, objectType) }
90+ val properties = field.selectionSet.selections.flatMap {
91+ when (it) {
92+ is Field -> listOf (projectField(variable, it, objectType, ctx))
93+ is InlineFragment -> projectInlineFragment(variable, it, objectType, ctx)
94+ is FragmentSpread -> projectNamedFragments(variable, it, objectType, ctx)
95+ else -> emptyList()
96+ }
97+ }
9598
9699 val projection = properties.map { it.query }.joinToString(" ," , " { " , " }" )
97- val params = properties.map{ it.params }.reduce { res,map -> res + map }
100+ val params = properties.map{ it.params }.fold(emptyMap< String , Any ?>()) { res, map -> res + map }
98101 return Cypher (" $variable $projection " ,params)
99102 }
100103
101- private fun resolveField (variable : String , field : Field , type : GraphQLObjectType ) : Cypher {
104+ private fun projectField (variable : String , field : Field , type : GraphQLObjectType , ctx : Context ) : Cypher {
102105 val fieldDefinition = type.getFieldDefinition(field.name)
103106 return if (inner(fieldDefinition.type) is GraphQLObjectType ) {
104- val patternComprehensions = projectRelationship(variable, field, fieldDefinition)
107+ val patternComprehensions = projectRelationship(variable, field, fieldDefinition, ctx )
105108 Cypher (field.aliasOrName() + " :" + patternComprehensions.query, patternComprehensions.params)
106109 } else Cypher (" ." + field.aliasOrName())
107110 }
108111
109- private fun projectRelationship (variable : String , field : Field , fieldDefinition : GraphQLFieldDefinition ): Cypher {
112+ fun projectNamedFragments (variable : String , fragmentSpread : FragmentSpread , type : GraphQLObjectType , ctx : Context ) =
113+ ctx.fragments.getValue(fragmentSpread.name).let {
114+ projectFragment(it.typeCondition.name, type, variable, ctx, it.selectionSet)
115+ }
116+
117+ private fun projectFragment (fragmentTypeName : String? , type : GraphQLObjectType , variable : String , ctx : Context , selectionSet : SelectionSet ): List <Cypher > {
118+ val fragmentType = schema.getType(fragmentTypeName)!! as GraphQLObjectType
119+ if (fragmentType == type) {
120+ // these are the nested fields of the fragment
121+ // it could be that we have to adapt the variable name too, and perhaps add some kind of rename
122+ return selectionSet.selections.filterIsInstance<Field >().map { projectField(variable, it, fragmentType, ctx) }
123+ } else {
124+ return emptyList()
125+ }
126+ }
127+
128+ fun projectInlineFragment (variable : String , fragment : InlineFragment , type : GraphQLObjectType , ctx : Context ) =
129+ projectFragment(fragment.typeCondition.name, type, variable, ctx, fragment.selectionSet)
130+
131+
132+ private fun projectRelationship (variable : String , field : Field , fieldDefinition : GraphQLFieldDefinition , ctx : Context ): Cypher {
110133 val fieldType = fieldDefinition.type
111134 val innerType = inner(fieldType).name
112135 val fieldObjectType = schema.getType(innerType)
@@ -118,7 +141,7 @@ class Translator(val schema: GraphQLSchema) {
118141 val childVariable = field.name + fieldObjectType.name
119142 val childPattern = " $childVariable :$innerType "
120143 val where = where(childVariable,fieldDefinition,propertyArguments(field))
121- val fieldProjection = projectFields(childVariable, field, fieldObjectType)
144+ val fieldProjection = projectFields(childVariable, field, fieldObjectType, ctx )
122145 val comprehension = " [($variable )$inArrow -[:${relType} ]-$outArrow ($childPattern )${where.query} | ${fieldProjection.query} ]"
123146 val skipLimit = skipLimit(field.arguments)
124147 val slice = slice(skipLimit,fieldType.isList())
0 commit comments