Skip to content

Adding support for list type property filters #285

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
Jan 25, 2023
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
11 changes: 7 additions & 4 deletions core/src/main/kotlin/org/neo4j/graphql/AugmentationHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ abstract class AugmentationHandler(
if (addFieldOperations && !field.isNativeId()) {
val typeDefinition = field.type.resolve()
?: throw IllegalArgumentException("type ${field.type.name()} cannot be resolved")
FieldOperator.forType(typeDefinition, field.type.inner().isNeo4jType())
FieldOperator.forType(typeDefinition, field.type.inner().isNeo4jType(), field.type.isList())
.map { op ->
val wrappedType: Type<*> = when {
op.list -> ListType(NonNullType(TypeName(type.name())))
Expand Down Expand Up @@ -160,11 +160,14 @@ abstract class AugmentationHandler(
if (field.isRelationship()) {
RelationOperator.createRelationFilterFields(type, field, filterType, builder)
} else {
FieldOperator.forType(typeDefinition, field.type.inner().isNeo4jType())
.forEach { op -> builder.addFilterField(op.fieldName(field.name), op.list, filterType, field.description) }
FieldOperator.forType(typeDefinition, field.type.inner().isNeo4jType(), field.type.isList())
.forEach { op -> when {
field.type.isList() -> builder.addArrayFilterField(op.fieldName(field.name), filterType, field.description)
else -> builder.addFilterField(op.fieldName(field.name), op.list, filterType, field.description)
}}
if (typeDefinition.isNeo4jSpatialType()) {
val distanceFilterType = getSpatialDistanceFilter(neo4jTypeDefinitionRegistry.getUnwrappedType(filterType) as TypeDefinition<*>)
FieldOperator.forType(distanceFilterType, true)
FieldOperator.forType(distanceFilterType, true, field.type.isList())
.forEach { op -> builder.addFilterField(op.fieldName(field.name + NEO4j_POINT_DISTANCE_FILTER_SUFFIX), op.list, NEO4j_POINT_DISTANCE_FILTER) }
}
}
Expand Down
11 changes: 11 additions & 0 deletions core/src/main/kotlin/org/neo4j/graphql/GraphQLExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,17 @@ fun InputObjectTypeDefinition.Builder.addFilterField(fieldName: String, isList:
this.inputValueDefinition(inputField.build())
}

fun InputObjectTypeDefinition.Builder.addArrayFilterField(fieldName: String, filterType: String, description: Description? = null) {
val inputField = InputValueDefinition.newInputValueDefinition()
.name(fieldName)
.type(ListType(NonNullType(TypeName(filterType))))
if (description != null) {
inputField.description(description)
}

this.inputValueDefinition(inputField.build())
}

fun TypeDefinitionRegistry.queryTypeName() = this.getOperationType("query") ?: "Query"
fun TypeDefinitionRegistry.mutationTypeName() = this.getOperationType("mutation") ?: "Mutation"
fun TypeDefinitionRegistry.subscriptionTypeName() = this.getOperationType("subscription") ?: "Subscription"
Expand Down
18 changes: 13 additions & 5 deletions core/src/main/kotlin/org/neo4j/graphql/Predicates.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import graphql.language.*
import graphql.language.TypeDefinition
import graphql.schema.GraphQLFieldDefinition
import graphql.schema.GraphQLFieldsContainer
import org.neo4j.cypherdsl.core.Condition
import org.neo4j.cypherdsl.core.Expression
import org.neo4j.cypherdsl.core.Property
import org.neo4j.cypherdsl.core.PropertyContainer
import org.neo4j.cypherdsl.core.*
import org.neo4j.cypherdsl.core.Predicates.OngoingListBasedPredicateFunction
import org.slf4j.LoggerFactory

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

private fun createArrayPredicate(factory: (SymbolicName) -> OngoingListBasedPredicateFunction) = { lhs: Expression, rhs: Expression ->
val x: SymbolicName = org.neo4j.cypherdsl.core.Cypher.name("x")
factory(x).`in`(lhs).where(x.`in`(rhs))
}

enum class FieldOperator(
val suffix: String,
private val conditionCreator: (Expression, Expression) -> Condition,
Expand Down Expand Up @@ -39,6 +42,10 @@ enum class FieldOperator(
EW("_ends_with", { lhs, rhs -> lhs.endsWith(rhs) }),
MATCHES("_matches", { lhs, rhs -> lhs.matches(rhs) }),

INCLUDES_ALL("_includes_all", createArrayPredicate(Predicates::all), list = true),
INCLUDES_SOME("_includes_some", createArrayPredicate(Predicates::any), list = true),
INCLUDES_NONE("_includes_none", createArrayPredicate(Predicates::none), list = true),
INCLUDES_SINGLE("_includes_single", createArrayPredicate(Predicates::single), list = true),

DISTANCE(NEO4j_POINT_DISTANCE_FILTER_SUFFIX, { lhs, rhs -> lhs.isEqualTo(rhs) }, distance = true),
DISTANCE_LT(NEO4j_POINT_DISTANCE_FILTER_SUFFIX + "_lt", { lhs, rhs -> lhs.lt(rhs) }, distance = true),
Expand Down Expand Up @@ -109,8 +116,9 @@ enum class FieldOperator(

companion object {

fun forType(type: TypeDefinition<*>, isNeo4jType: Boolean): List<FieldOperator> =
fun forType(type: TypeDefinition<*>, isNeo4jType: Boolean, isList: Boolean): List<FieldOperator> =
when {
isList -> listOf(EQ, NEQ, INCLUDES_ALL, INCLUDES_NONE, INCLUDES_SOME, INCLUDES_SINGLE)
type.name == TypeBoolean.name -> listOf(EQ, NEQ)
type.name == NEO4j_POINT_DISTANCE_FILTER -> listOf(EQ, LT, LTE, GT, GTE)
type.isNeo4jSpatialType() -> listOf(EQ, NEQ)
Expand Down
12 changes: 6 additions & 6 deletions core/src/test/resources/augmentation-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
interface HasMovies { movies:[Movie] }
type Person0 { name: String, born: _Neo4jTime, location: _Neo4jPoint }
type Person1 { name: String, born: _Neo4jDate }
type Person2 { name: String, age: [Int], born: _Neo4jDateTime }
type Person2 { name: String, age: Int, born: _Neo4jDateTime }
type Person3 { name: String!, born: _Neo4jLocalTime }
type Person4 { id:ID!, name: String, born: _Neo4jLocalDateTime }
type Person5 implements HasMovies { id:ID!, movies:[Movie] @relation(name: "LIKES")}
Expand Down Expand Up @@ -105,7 +105,7 @@ type Mutation {
createKnows4(json: DynamicProperties, knows_id: ID!, source_id: ID!): Knows4!
createMovie(id: ID!): Movie!
createPerson1(born: _Neo4jDateInput, name: String): Person1!
createPerson2(age: [Int], born: _Neo4jDateTimeInput, name: String): Person2!
createPerson2(age: Int, born: _Neo4jDateTimeInput, name: String): Person2!
createPerson3(born: _Neo4jLocalTimeInput, name: String!): Person3!
createPerson4(born: _Neo4jLocalDateTimeInput, id: ID!, name: String): Person4!
createPerson5(id: ID!): Person5!
Expand Down Expand Up @@ -146,7 +146,7 @@ type Person1 {
}

type Person2 {
age: [Int]
age: Int
born: _Neo4jDateTime
name: String
}
Expand Down Expand Up @@ -421,7 +421,7 @@ type Person1 {
}

type Person2 {
age: [Int]
age: Int
born: _Neo4jDateTime
name: String
}
Expand Down Expand Up @@ -454,7 +454,7 @@ type Query {
knows4(_id: ID, filter: _Knows4Filter, first: Int, offset: Int, orderBy: [_Knows4Ordering!]): [Knows4!]!
movie(filter: _MovieFilter, first: Int, id: ID, id_contains: ID, id_ends_with: ID, id_gt: ID, id_gte: ID, id_in: [ID!], id_lt: ID, id_lte: ID, id_matches: ID, id_not: ID, id_not_contains: ID, id_not_ends_with: ID, id_not_in: [ID!], id_not_starts_with: ID, id_starts_with: ID, offset: Int, orderBy: [_MovieOrdering!]): [Movie!]!
person1(born: _Neo4jDateInput, born_in: [_Neo4jDateInput!], born_not: _Neo4jDateInput, born_not_in: [_Neo4jDateInput!], filter: _Person1Filter, first: Int, name: String, name_contains: String, name_ends_with: String, name_gt: String, name_gte: String, name_in: [String!], name_lt: String, name_lte: String, name_matches: String, name_not: String, name_not_contains: String, name_not_ends_with: String, name_not_in: [String!], name_not_starts_with: String, name_starts_with: String, offset: Int, orderBy: [_Person1Ordering!]): [Person1!]!
person2(age: [Int], age_gt: [Int], age_gte: [Int], age_in: [Int!], age_lt: [Int], age_lte: [Int], age_not: [Int], age_not_in: [Int!], born: _Neo4jDateTimeInput, born_in: [_Neo4jDateTimeInput!], born_not: _Neo4jDateTimeInput, born_not_in: [_Neo4jDateTimeInput!], filter: _Person2Filter, first: Int, name: String, name_contains: String, name_ends_with: String, name_gt: String, name_gte: String, name_in: [String!], name_lt: String, name_lte: String, name_matches: String, name_not: String, name_not_contains: String, name_not_ends_with: String, name_not_in: [String!], name_not_starts_with: String, name_starts_with: String, offset: Int, orderBy: [_Person2Ordering!]): [Person2!]!
person2(age: Int, age_gt: Int, age_gte: Int, age_in: [Int!], age_lt: Int, age_lte: Int, age_not: Int, age_not_in: [Int!], born: _Neo4jDateTimeInput, born_in: [_Neo4jDateTimeInput!], born_not: _Neo4jDateTimeInput, born_not_in: [_Neo4jDateTimeInput!], filter: _Person2Filter, first: Int, name: String, name_contains: String, name_ends_with: String, name_gt: String, name_gte: String, name_in: [String!], name_lt: String, name_lte: String, name_matches: String, name_not: String, name_not_contains: String, name_not_ends_with: String, name_not_in: [String!], name_not_starts_with: String, name_starts_with: String, offset: Int, orderBy: [_Person2Ordering!]): [Person2!]!
person3(born: _Neo4jLocalTimeInput, born_in: [_Neo4jLocalTimeInput!], born_not: _Neo4jLocalTimeInput, born_not_in: [_Neo4jLocalTimeInput!], filter: _Person3Filter, first: Int, name: String, name_contains: String, name_ends_with: String, name_gt: String, name_gte: String, name_in: [String!], name_lt: String, name_lte: String, name_matches: String, name_not: String, name_not_contains: String, name_not_ends_with: String, name_not_in: [String!], name_not_starts_with: String, name_starts_with: String, offset: Int, orderBy: [_Person3Ordering!]): [Person3!]!
person4(born: _Neo4jLocalDateTimeInput, born_in: [_Neo4jLocalDateTimeInput!], born_not: _Neo4jLocalDateTimeInput, born_not_in: [_Neo4jLocalDateTimeInput!], filter: _Person4Filter, first: Int, id: ID, id_contains: ID, id_ends_with: ID, id_gt: ID, id_gte: ID, id_in: [ID!], id_lt: ID, id_lte: ID, id_matches: ID, id_not: ID, id_not_contains: ID, id_not_ends_with: ID, id_not_in: [ID!], id_not_starts_with: ID, id_starts_with: ID, name: String, name_contains: String, name_ends_with: String, name_gt: String, name_gte: String, name_in: [String!], name_lt: String, name_lte: String, name_matches: String, name_not: String, name_not_contains: String, name_not_ends_with: String, name_not_in: [String!], name_not_starts_with: String, name_starts_with: String, offset: Int, orderBy: [_Person4Ordering!]): [Person4!]!
person5(filter: _Person5Filter, first: Int, id: ID, id_contains: ID, id_ends_with: ID, id_gt: ID, id_gte: ID, id_in: [ID!], id_lt: ID, id_lte: ID, id_matches: ID, id_not: ID, id_not_contains: ID, id_not_ends_with: ID, id_not_in: [ID!], id_not_starts_with: ID, id_starts_with: ID, offset: Int, orderBy: [_Person5Ordering!]): [Person5!]!
Expand Down Expand Up @@ -1197,7 +1197,7 @@ type Person1 {
}

type Person2 {
age: [Int]
age: Int
born: _Neo4jDateTime
name: String
}
Expand Down
Loading