Skip to content

use variable everywhere #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .run/Update All tests.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Update All tests" type="JUnit" factoryName="JUnit">
<module name="neo4j-graphql-java" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="uniqueId" />
<option name="VM_PARAMETERS" value="-ea -Dneo4j-graphql-java.update-test-file=true" />
<uniqueIds>
<uniqueId value="[engine:junit-jupiter]/[class:org.neo4j.graphql.CypherTests]" />
</uniqueIds>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-cypher-dsl</artifactId>
<version>2020.0.1</version>
<version>2021.0.1</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
Expand Down
10 changes: 0 additions & 10 deletions core/src/main/kotlin/org/neo4j/cypherdsl/core/PassThrough.kt

This file was deleted.

7 changes: 1 addition & 6 deletions core/src/main/kotlin/org/neo4j/graphql/Cypher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ package org.neo4j.graphql

import graphql.schema.GraphQLType

data class Cypher @JvmOverloads constructor(val query: String, val params: Map<String, Any?> = emptyMap(), var type: GraphQLType? = null, var variable: String? = null) {
data class Cypher @JvmOverloads constructor(val query: String, val params: Map<String, Any?> = emptyMap(), var type: GraphQLType? = null, val variable: String) {
fun with(p: Map<String, Any?>) = this.copy(params = this.params + p)
fun escapedQuery() = query.replace("\"", "\\\"").replace("'", "\\'")

companion object {
@JvmStatic
val EMPTY = Cypher("")
}
}
7 changes: 3 additions & 4 deletions core/src/main/kotlin/org/neo4j/graphql/DirectiveConstants.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package org.neo4j.graphql

class DirectiveConstants {

companion object {

const val RELATION = "relation"
const val RELATION_NAME = "name"
const val RELATION_DIRECTION = "direction"
const val RELATION_DIRECTION_IN = "IN"
const val RELATION_DIRECTION_OUT = "OUT"
const val RELATION_DIRECTION_BOTH = "BOTH"
const val RELATION_FROM = "from"
const val RELATION_TO = "to"

Expand All @@ -20,4 +19,4 @@ class DirectiveConstants {
const val DYNAMIC = "dynamic"
const val DYNAMIC_PREFIX = "prefix"
}
}
}
41 changes: 12 additions & 29 deletions core/src/main/kotlin/org/neo4j/graphql/DynamicProperties.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,35 +29,18 @@ object DynamicProperties {

@Throws(CoercingParseLiteralException::class)
private fun parse(input: Any, variables: Map<String, Any>): Any? {
if (input !is Value<*>) {
throw CoercingParseLiteralException("Expected AST type 'StringValue' but was '${input::class.java.simpleName}'.")
} else if (input is NullValue) {
return null
} else if (input is FloatValue) {
return input.value
} else if (input is StringValue) {
return input.value
} else if (input is IntValue) {
return input.value
} else if (input is BooleanValue) {
return input.isValue
} else if (input is EnumValue) {
return input.name
} else if (input is VariableReference) {
val varName = input.name
return variables[varName]
} else {
val values: List<*>
return when (input) {
is ArrayValue -> {
values = input.values
values.map { v -> parse(v, variables) }
}
is ObjectValue -> {
throw IllegalArgumentException("deep structures not supported for dynamic properties")
}
else -> Assert.assertShouldNeverHappen("We have covered all Value types")
}
return when (input) {
!is Value<*> -> throw CoercingParseLiteralException("Expected AST type 'StringValue' but was '${input::class.java.simpleName}'.")
is NullValue -> null
is FloatValue -> input.value
is StringValue -> input.value
is IntValue -> input.value
is BooleanValue -> input.isValue
is EnumValue -> input.name
is VariableReference -> variables[input.name]
is ArrayValue -> input.values.map { v -> parse(v, variables) }
is ObjectValue -> throw IllegalArgumentException("deep structures not supported for dynamic properties")
else -> Assert.assertShouldNeverHappen("We have covered all Value types")
}
}

Expand Down
29 changes: 21 additions & 8 deletions core/src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package org.neo4j.graphql

import graphql.language.VariableReference
import graphql.schema.GraphQLArgument
import graphql.schema.GraphQLInputType
import graphql.schema.GraphQLType
import java.io.PrintWriter
import java.io.StringWriter

fun Throwable.stackTraceAsString(): String {
val sw = StringWriter()
this.printStackTrace(PrintWriter(sw))
return sw.toString()
}
import org.neo4j.cypherdsl.core.*

fun <T> Iterable<T>.joinNonEmpty(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
return if (iterator().hasNext()) joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() else ""
Expand All @@ -23,3 +17,22 @@ fun input(name: String, type: GraphQLType): GraphQLArgument {
.type((type.ref() as? GraphQLInputType)
?: throw IllegalArgumentException("${type.innerName()} is not allowed for input")).build()
}

fun queryParameter(value: Any?, vararg parts: String?): Parameter<Any> {
val name = when (value) {
is VariableReference -> value.name
else -> normalizeName(*parts)
}
return org.neo4j.cypherdsl.core.Cypher.parameter(name).withValue(value?.toJavaValue())
}

fun normalizeName(vararg parts: String?) = parts.mapNotNull { it?.capitalize() }.filter { it.isNotBlank() }.joinToString("").decapitalize()
//fun normalizeName(vararg parts: String?) = parts.filterNot { it.isNullOrBlank() }.joinToString("_")

fun PropertyContainer.id(): FunctionInvocation = when (this) {
is Node -> Functions.id(this)
is Relationship -> Functions.id(this)
else -> throw IllegalArgumentException("Id can only be retrieved for Nodes or Relationships")
}

fun String.toCamelCase(): String = Regex("[\\W_]([a-z])").replace(this) { it.groupValues[1].toUpperCase() }
111 changes: 49 additions & 62 deletions core/src/main/kotlin/org/neo4j/graphql/GraphQLExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import graphql.language.*
import graphql.schema.*
import org.neo4j.cypherdsl.core.Node
import org.neo4j.cypherdsl.core.Relationship
import org.neo4j.cypherdsl.core.SymbolicName
import org.neo4j.graphql.DirectiveConstants.Companion.CYPHER
import org.neo4j.graphql.DirectiveConstants.Companion.CYPHER_STATEMENT
import org.neo4j.graphql.DirectiveConstants.Companion.DYNAMIC
import org.neo4j.graphql.DirectiveConstants.Companion.DYNAMIC_PREFIX
import org.neo4j.graphql.DirectiveConstants.Companion.PROPERTY
import org.neo4j.graphql.DirectiveConstants.Companion.PROPERTY_NAME
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_DIRECTION
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_DIRECTION_BOTH
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_DIRECTION_IN
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_DIRECTION_OUT
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_FROM
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_NAME
import org.neo4j.graphql.DirectiveConstants.Companion.RELATION_TO
import org.neo4j.graphql.handler.projection.ProjectionBase
import java.math.BigDecimal
import java.math.BigInteger

fun Type<Type<*>>.name(): String? = if (this.inner() is TypeName) (this.inner() as TypeName).name else null
fun Type<Type<*>>.inner(): Type<Type<*>> = when (this) {
Expand All @@ -34,7 +34,7 @@ fun GraphQLType.inner(): GraphQLType = when (this) {
}

fun GraphQLType.name(): String? = (this as? GraphQLNamedType)?.name
fun GraphQLType.requiredName(): String = requireNotNull(name()) { -> "name is required but cannot be determined for " + this.javaClass }
fun GraphQLType.requiredName(): String = requireNotNull(name()) { "name is required but cannot be determined for " + this.javaClass }

fun GraphQLType.isList() = this is GraphQLList || (this is GraphQLNonNull && this.wrappedType is GraphQLList)
fun GraphQLType.isScalar() = this.inner().let { it is GraphQLScalarType || it.innerName().startsWith("_Neo4j") }
Expand All @@ -44,7 +44,6 @@ fun GraphQLFieldDefinition.isNeo4jType(): Boolean = this.type.isNeo4jType()

fun GraphQLFieldDefinition.isRelationship() = !type.isNeo4jType() && this.type.inner().let { it is GraphQLFieldsContainer }

fun GraphQLFieldsContainer.hasRelationship(name: String) = this.getFieldDefinition(name)?.isRelationship() ?: false
fun GraphQLDirectiveContainer.isRelationType() = getDirective(DirectiveConstants.RELATION) != null
fun GraphQLFieldsContainer.isRelationType() = (this as? GraphQLDirectiveContainer)?.getDirective(DirectiveConstants.RELATION) != null
fun GraphQLFieldsContainer.relationshipFor(name: String): RelationshipInfo? {
Expand All @@ -69,16 +68,16 @@ fun GraphQLFieldsContainer.relationshipFor(name: String): RelationshipInfo? {

val relInfo = relDetails(fieldObjectType, relDirective)

return if (inverse) relInfo.copy(out = relInfo.out?.let { !it }, startField = relInfo.endField, endField = relInfo.startField) else relInfo
return if (inverse) relInfo.copy(direction = relInfo.direction.invert(), startField = relInfo.endField, endField = relInfo.startField) else relInfo
}

fun GraphQLFieldsContainer.getValidTypeLabels(schema: GraphQLSchema): List<String> {
if (this is GraphQLObjectType) {
return listOf(this.quotedLabel())
return listOf(this.label())
}
if (this is GraphQLInterfaceType) {
return schema.getImplementations(this)
.mapNotNull { it.quotedLabel() }
.mapNotNull { it.label() }
}
return emptyList()
}
Expand All @@ -92,14 +91,6 @@ fun GraphQLFieldsContainer.label(): String = when {
else -> name
}

fun GraphQLFieldsContainer.quotedLabel() = this.label().quote()

fun GraphQLFieldsContainer.allLabels() = when {
this.isRelationType() -> this.quotedLabel()
else -> (listOf(name) + ((this as? GraphQLObjectType)?.interfaces?.map { it.name } ?: emptyList()))
.map { it.quote() }
.joinToString(":")
}

fun GraphQLFieldsContainer.relevantFields() = fieldDefinitions
.filter { it.type.isScalar() || it.isNeo4jType() }
Expand All @@ -113,7 +104,9 @@ fun GraphQLFieldsContainer.relationship(): RelationshipInfo? {
val relType = directiveResolver(RELATION_NAME, "")!!
val startField = directiveResolver(RELATION_FROM, null)
val endField = directiveResolver(RELATION_TO, null)
return RelationshipInfo(this, relType, null, startField, endField)
val direction = directiveResolver(RELATION_DIRECTION, null)?.let { RelationDirection.valueOf(it) }
?: RelationDirection.OUT
return RelationshipInfo(this, relType, direction, startField, endField)
}

fun GraphQLType.ref(): GraphQLType = when (this) {
Expand All @@ -127,23 +120,21 @@ fun GraphQLType.ref(): GraphQLType = when (this) {

fun relDetails(type: GraphQLFieldsContainer, relDirective: GraphQLDirective): RelationshipInfo {
val relType = relDirective.getArgument(RELATION_NAME, "")!!
val outgoing = when (relDirective.getArgument<String>(RELATION_DIRECTION, null)) {
RELATION_DIRECTION_IN -> false
RELATION_DIRECTION_BOTH -> null
RELATION_DIRECTION_OUT -> true
else -> throw IllegalStateException("Unknown direction ${relDirective.getArgument<String>(RELATION_DIRECTION, null)}")
}
val direction = relDirective.getArgument<String>(RELATION_DIRECTION, null)
?.let { RelationDirection.valueOf(it) }
?: RelationDirection.OUT

return RelationshipInfo(type,
relType,
outgoing,
direction,
relDirective.getArgument<String>(RELATION_FROM, null),
relDirective.getArgument<String>(RELATION_TO, null))
}

data class RelationshipInfo(
val type: GraphQLFieldsContainer,
val relType: String,
val out: Boolean?,
val direction: RelationDirection,
val startField: String? = null,
val endField: String? = null,
val isRelFromType: Boolean = false
Expand All @@ -154,10 +145,10 @@ data class RelationshipInfo(
val declaringType: GraphQLFieldsContainer
)

val arrows = when (out) {
false -> "<" to ""
true -> "" to ">"
null -> "" to ""
val arrows = when (direction) {
RelationDirection.IN -> "<" to ""
RelationDirection.OUT -> "" to ">"
RelationDirection.BOTH -> "" to ""
}

val typeName: String get() = this.type.name
Expand All @@ -173,20 +164,26 @@ data class RelationshipInfo(
val relType = relFieldDefinition.type.inner() as? GraphQLFieldsContainer
?: throw IllegalArgumentException("type ${relFieldDefinition.type.innerName()} not found")
return relType.fieldDefinitions.filter { it.isID() }
.map { RelatedField("${relFieldName}_${it.name}", it, relType) }
.map {
// TODO b/c we need to stay backwards kompatible this is not caml case but with underscore
//val filedName = normalizeName(relFieldName, it.name)
val filedName = "${relFieldName}_${it.name}"
RelatedField(filedName, it, relType)
}
.firstOrNull()
}

fun createRelation(start: Node, end: Node): Relationship =
when (this.out) {
false -> start.relationshipFrom(end, this.relType)
true -> start.relationshipTo(end, this.relType)
null -> start.relationshipBetween(end, this.relType)
when (this.direction) {
RelationDirection.IN -> start.relationshipFrom(end, this.relType)
RelationDirection.OUT -> start.relationshipTo(end, this.relType)
RelationDirection.BOTH -> start.relationshipBetween(end, this.relType)
}
}

fun Field.aliasOrName() = (this.alias ?: this.name).quote()
fun Field.contextualize(variable: String) = variable + (this.alias ?: this.name).capitalize()
fun Field.aliasOrName(): String = (this.alias ?: this.name)
fun Field.contextualize(variable: String) = variable + this.aliasOrName().capitalize()
fun Field.contextualize(variable: SymbolicName) = variable.value + this.aliasOrName().capitalize()

fun GraphQLType.innerName(): String = inner().name()
?: throw IllegalStateException("inner name cannot be retrieved for " + this.javaClass)
Expand All @@ -209,27 +206,7 @@ fun <T> GraphQLDirective.getArgument(argumentName: String, defaultValue: T?): T?
?: throw IllegalStateException("No default value for @${this.name}::$argumentName")
}

fun GraphQLFieldDefinition.cypherDirective(): Cypher? = getDirectiveArgument<String>(CYPHER, CYPHER_STATEMENT, null)
?.let { statement -> Cypher(statement) }

fun String.quote() = if (isJavaIdentifier()) this else "`$this`"

fun String.isJavaIdentifier() =
this[0].isJavaIdentifierStart() &&
this.substring(1).all { it.isJavaIdentifierPart() }

@Suppress("SimplifiableCallChain")
fun Value<Value<*>>.toCypherString(): String = when (this) {
is StringValue -> "'" + this.value + "'"
is EnumValue -> "'" + this.name + "'"
is NullValue -> "null"
is BooleanValue -> this.isValue.toString()
is FloatValue -> this.value.toString()
is IntValue -> this.value.toString()
is VariableReference -> "$" + this.name
is ArrayValue -> this.values.map { it.toCypherString() }.joinToString(",", "[", "]")
else -> throw IllegalStateException("Unhandled value $this")
}
fun GraphQLFieldDefinition.cypherDirective(): String? = getDirectiveArgument<String>(CYPHER, CYPHER_STATEMENT, null)

fun Any.toJavaValue() = when (this) {
is Value<*> -> this.toJavaValue()
Expand All @@ -249,11 +226,6 @@ fun Value<*>.toJavaValue(): Any? = when (this) {
else -> throw IllegalStateException("Unhandled value $this")
}

fun paramName(variable: String, argName: String, value: Any?): String = when (value) {
is VariableReference -> value.name
else -> "$variable${argName.capitalize()}"
}

fun GraphQLFieldDefinition.isID() = this.type.inner() == Scalars.GraphQLID
fun GraphQLFieldDefinition.isNativeId() = this.name == ProjectionBase.NATIVE_ID
fun GraphQLFieldsContainer.getIdField() = this.fieldDefinitions.find { it.isID() }
Expand All @@ -274,3 +246,18 @@ fun GraphQLInputObjectType.Builder.addFilterField(fieldName: String, isList: Boo
fun GraphQLSchema.queryTypeName() = this.queryType?.name ?: "Query"
fun GraphQLSchema.mutationTypeName() = this.mutationType?.name ?: "Mutation"
fun GraphQLSchema.subscriptionTypeName() = this.subscriptionType?.name ?: "Subscription"

fun Any?.asGraphQLValue(): Value<*> = when (this) {
null -> NullValue.newNullValue().build()
is Value<*> -> this
is Array<*> -> ArrayValue.newArrayValue().values(this.map { it.asGraphQLValue() }).build()
is Iterable<*> -> ArrayValue.newArrayValue().values(this.map { it.asGraphQLValue() }).build()
is Map<*, *> -> ObjectValue.newObjectValue().objectFields(this.map { entry -> ObjectField(entry.key as String, entry.value.asGraphQLValue()) }).build()
is Enum<*> -> EnumValue.newEnumValue().name(this.name).build()
is Int -> IntValue.newIntValue(BigInteger.valueOf(this.toLong())).build()
is Long -> IntValue.newIntValue(BigInteger.valueOf(this)).build()
is Number -> FloatValue.newFloatValue(BigDecimal.valueOf(this as Double)).build()
is Boolean -> BooleanValue.newBooleanValue(this).build()
is String -> StringValue.newStringValue(this).build()
else -> throw IllegalStateException("Cannot convert ${this.javaClass.name} into an graphql type")
}
Loading