diff --git a/pom.xml b/pom.xml
index 2453bf9af0..e275a5706c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
org.springframework.data
spring-data-neo4j
- 6.2.0-SNAPSHOT
+ 6.2.0-2343-SNAPSHOT
Spring Data Neo4j
Next generation Object-Graph-Mapping for Spring Data.
@@ -117,7 +117,7 @@
${skipTests}
${skipTests}
- 2.6.0-SNAPSHOT
+ 2.6.0-2228-SNAPSHOT
../../../../spring-data-commons/src/main/asciidoc
diff --git a/src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java b/src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java
index 25ddc676fb..2ef5dacb38 100644
--- a/src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java
+++ b/src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java
@@ -22,6 +22,7 @@
import org.apiguardian.api.API;
import org.neo4j.cypherdsl.core.Statement;
+import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
import org.springframework.lang.Nullable;
/**
@@ -106,6 +107,16 @@ interface FindWithQuery extends TerminatingFindWithoutQuery {
*/
TerminatingFind matching(String query, @Nullable Map parameter);
+ /**
+ * Creates an executable query based on fragments and parameters. Hardly useful outside framework-code
+ * and we actively discourage using this method.
+ *
+ * @param queryFragmentsAndParameters Encapsulated query fragements and parameters as created by the repository abstraction.
+ * @return new instance of {@link TerminatingFind}.
+ * @throws IllegalArgumentException if queryFragmentsAndParameters is {@literal null}.
+ */
+ TerminatingFind matching(QueryFragmentsAndParameters queryFragmentsAndParameters);
+
/**
* Set the filter query to be used.
*
diff --git a/src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java b/src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java
index c3af0fd1aa..fd212ca9b9 100644
--- a/src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java
+++ b/src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java
@@ -19,6 +19,7 @@
import java.util.List;
import java.util.Map;
+import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
import org.springframework.util.Assert;
/**
@@ -51,6 +52,7 @@ private static class ExecutableFindSupport
private final Class returnType;
private final String query;
private final Map parameters;
+ private final QueryFragmentsAndParameters queryFragmentsAndParameters;
ExecutableFindSupport(Neo4jTemplate template, Class> domainType, Class returnType, String query,
Map parameters) {
@@ -59,6 +61,16 @@ private static class ExecutableFindSupport
this.returnType = returnType;
this.query = query;
this.parameters = parameters;
+ this.queryFragmentsAndParameters = null;
+ }
+
+ ExecutableFindSupport(Neo4jTemplate template, Class> domainType, Class returnType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
+ this.template = template;
+ this.domainType = domainType;
+ this.returnType = returnType;
+ this.query = null;
+ this.parameters = null;
+ this.queryFragmentsAndParameters = queryFragmentsAndParameters;
}
@Override
@@ -79,6 +91,15 @@ public TerminatingFind matching(String query, Map parameters)
return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters);
}
+ @Override
+ @SuppressWarnings("HiddenField")
+ public TerminatingFind matching(QueryFragmentsAndParameters queryFragmentsAndParameters) {
+
+ Assert.notNull(queryFragmentsAndParameters, "Query fragments must not be null!");
+
+ return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters);
+ }
+
@Override
public T oneValue() {
@@ -95,7 +116,7 @@ public List all() {
}
private List doFind(TemplateSupport.FetchType fetchType) {
- return template.doFind(query, parameters, domainType, returnType, fetchType);
+ return template.doFind(query, parameters, domainType, returnType, fetchType, queryFragmentsAndParameters);
}
}
diff --git a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java
index 5d5d7e56d3..bd49abc3db 100644
--- a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java
+++ b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java
@@ -245,14 +245,19 @@ public ExecutableFind find(Class domainType) {
}
@SuppressWarnings("unchecked")
- List doFind(@Nullable String cypherQuery, @Nullable Map parameters, Class domainType, Class resultType, TemplateSupport.FetchType fetchType) {
+ List doFind(@Nullable String cypherQuery, @Nullable Map parameters, Class domainType, Class resultType, TemplateSupport.FetchType fetchType, @Nullable QueryFragmentsAndParameters queryFragmentsAndParameters) {
List intermediaResults = Collections.emptyList();
- if (cypherQuery == null && fetchType == TemplateSupport.FetchType.ALL) {
+ if (cypherQuery == null && queryFragmentsAndParameters == null && fetchType == TemplateSupport.FetchType.ALL) {
intermediaResults = doFindAll(domainType, resultType);
} else {
- ExecutableQuery executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
- parameters == null ? Collections.emptyMap() : parameters);
+ ExecutableQuery executableQuery;
+ if (queryFragmentsAndParameters == null) {
+ executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
+ parameters == null ? Collections.emptyMap() : parameters);
+ } else {
+ executableQuery = createExecutableQuery(domainType, resultType, queryFragmentsAndParameters);
+ }
switch (fetchType) {
case ALL:
intermediaResults = executableQuery.getResults();
diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java
index af2ee66c2e..16c22f6264 100644
--- a/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java
+++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java
@@ -23,6 +23,7 @@
import org.apiguardian.api.API;
import org.neo4j.cypherdsl.core.Statement;
+import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
import org.springframework.lang.Nullable;
/**
@@ -96,6 +97,16 @@ interface FindWithQuery extends TerminatingFindWithoutQuery {
*/
TerminatingFind matching(String query, @Nullable Map parameter);
+ /**
+ * Creates an executable query based on fragments and parameters. Hardly useful outside framework-code
+ * and we actively discourage using this method.
+ *
+ * @param queryFragmentsAndParameters Encapsulated query fragements and parameters as created by the repository abstraction.
+ * @return new instance of {@link FluentFindOperation.TerminatingFind}.
+ * @throws IllegalArgumentException if queryFragmentsAndParameters is {@literal null}.
+ */
+ TerminatingFind matching(QueryFragmentsAndParameters queryFragmentsAndParameters);
+
/**
* Set the filter query to be used.
*
diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java
index 9295aeefcc..658bfa7f5d 100644
--- a/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java
+++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java
@@ -21,6 +21,7 @@
import java.util.Collections;
import java.util.Map;
+import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
import org.springframework.util.Assert;
/**
@@ -54,6 +55,7 @@ private static class ExecutableFindSupport
private final Class returnType;
private final String query;
private final Map parameters;
+ private final QueryFragmentsAndParameters queryFragmentsAndParameters;
ExecutableFindSupport(ReactiveNeo4jTemplate template, Class> domainType, Class returnType, String query,
Map parameters) {
@@ -62,6 +64,16 @@ private static class ExecutableFindSupport
this.returnType = returnType;
this.query = query;
this.parameters = parameters;
+ this.queryFragmentsAndParameters = null;
+ }
+
+ ExecutableFindSupport(ReactiveNeo4jTemplate template, Class> domainType, Class returnType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
+ this.template = template;
+ this.domainType = domainType;
+ this.returnType = returnType;
+ this.query = null;
+ this.parameters = null;
+ this.queryFragmentsAndParameters = queryFragmentsAndParameters;
}
@Override
@@ -82,6 +94,13 @@ public TerminatingFind matching(String query, Map parameters)
return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters);
}
+ @Override
+ @SuppressWarnings("HiddenField")
+ public TerminatingFind matching(QueryFragmentsAndParameters queryFragmentsAndParameters) {
+
+ return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters);
+ }
+
@Override
public Mono one() {
return doFind(TemplateSupport.FetchType.ONE).single();
@@ -93,7 +112,7 @@ public Flux all() {
}
private Flux doFind(TemplateSupport.FetchType fetchType) {
- return template.doFind(query, parameters, domainType, returnType, fetchType);
+ return template.doFind(query, parameters, domainType, returnType, fetchType, queryFragmentsAndParameters);
}
}
diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java
index d9a9b2bf3b..6ea5dec5d6 100644
--- a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java
+++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java
@@ -225,14 +225,19 @@ public ExecutableFind find(Class domainType) {
}
@SuppressWarnings("unchecked")
- Flux doFind(@Nullable String cypherQuery, @Nullable Map parameters, Class domainType, Class resultType, TemplateSupport.FetchType fetchType) {
+ Flux doFind(@Nullable String cypherQuery, @Nullable Map parameters, Class domainType, Class resultType, TemplateSupport.FetchType fetchType, @Nullable QueryFragmentsAndParameters queryFragmentsAndParameters) {
Flux intermediaResults = null;
- if (cypherQuery == null && fetchType == TemplateSupport.FetchType.ALL) {
+ if (cypherQuery == null && queryFragmentsAndParameters == null && fetchType == TemplateSupport.FetchType.ALL) {
intermediaResults = doFindAll(domainType, resultType);
} else {
- Mono> executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
- parameters == null ? Collections.emptyMap() : parameters);
+ Mono> executableQuery;
+ if (queryFragmentsAndParameters == null) {
+ executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
+ parameters == null ? Collections.emptyMap() : parameters);
+ } else {
+ executableQuery = createExecutableQuery(domainType, resultType, queryFragmentsAndParameters);
+ }
switch (fetchType) {
case ALL:
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/FetchableFluentQueryByExample.java b/src/main/java/org/springframework/data/neo4j/repository/query/FetchableFluentQueryByExample.java
new file mode 100644
index 0000000000..c87dc8bd5d
--- /dev/null
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/FetchableFluentQueryByExample.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2011-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.neo4j.repository.query;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.LongSupplier;
+import java.util.stream.Stream;
+
+import org.apiguardian.api.API;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.neo4j.core.FluentFindOperation;
+import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
+import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
+import org.springframework.data.support.PageableExecutionUtils;
+import org.springframework.lang.Nullable;
+
+/**
+ * Immutable implementation of a {@link FetchableFluentQuery}. All
+ * methods that return a {@link FetchableFluentQuery} return a new instance, the original instance won't be
+ * modified.
+ *
+ * @author Michael J. Simons
+ * @param Source type
+ * @param Result type
+ * @since 6.2
+ */
+@API(status = API.Status.INTERNAL, since = "6.2")
+final class FetchableFluentQueryByExample extends FluentQuerySupport implements FetchableFluentQuery {
+
+ private final Neo4jMappingContext mappingContext;
+
+ private final Example example;
+
+ private final FluentFindOperation findOperation;
+
+ private final Function, Long> countOperation;
+
+ private final Function, Boolean> existsOperation;
+
+ FetchableFluentQueryByExample(
+ Example example,
+ Class resultType,
+ Neo4jMappingContext mappingContext,
+ FluentFindOperation findOperation,
+ Function, Long> countOperation,
+ Function, Boolean> existsOperation
+ ) {
+ this(example, resultType, mappingContext, findOperation, countOperation, existsOperation, Sort.unsorted(),
+ null);
+ }
+
+ FetchableFluentQueryByExample(
+ Example example,
+ Class resultType,
+ Neo4jMappingContext mappingContext,
+ FluentFindOperation findOperation,
+ Function, Long> countOperation,
+ Function, Boolean> existsOperation,
+ Sort sort,
+ @Nullable Collection properties
+ ) {
+ super(resultType, sort, properties);
+ this.mappingContext = mappingContext;
+ this.example = example;
+ this.findOperation = findOperation;
+ this.countOperation = countOperation;
+ this.existsOperation = existsOperation;
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public FetchableFluentQuery sortBy(Sort sort) {
+
+ return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
+ this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public FetchableFluentQuery as(Class resultType) {
+
+ return new FetchableFluentQueryByExample<>(this.example, resultType, this.mappingContext, this.findOperation,
+ this.countOperation, this.existsOperation);
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public FetchableFluentQuery project(Collection properties) {
+
+ return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
+ this.countOperation, this.existsOperation, this.sort, mergeProperties(properties));
+ }
+
+ @Override
+ public R oneValue() {
+
+ return findOperation.find(example.getProbeType())
+ .as(resultType)
+ .matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
+ createIncludedFieldsPredicate()))
+ .oneValue();
+ }
+
+ @Override
+ public R firstValue() {
+
+ List all = all();
+ return all.isEmpty() ? null : all.get(0);
+ }
+
+ @Override
+ public List all() {
+
+ return findOperation.find(example.getProbeType())
+ .as(resultType)
+ .matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
+ createIncludedFieldsPredicate()))
+ .all();
+ }
+
+ @Override
+ public Page page(Pageable pageable) {
+
+ List page = findOperation.find(example.getProbeType())
+ .as(resultType)
+ .matching(QueryFragmentsAndParameters.forExample(mappingContext, example, pageable,
+ createIncludedFieldsPredicate()))
+ .all();
+
+ LongSupplier totalCountSupplier = this::count;
+ return PageableExecutionUtils.getPage(page, pageable, totalCountSupplier);
+ }
+
+ @Override
+ public Stream stream() {
+ return all().stream();
+ }
+
+ @Override
+ public long count() {
+ return countOperation.apply(example);
+ }
+
+ @Override
+ public boolean exists() {
+ return existsOperation.apply(example);
+ }
+}
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/FetchableFluentQueryByPredicate.java b/src/main/java/org/springframework/data/neo4j/repository/query/FetchableFluentQueryByPredicate.java
new file mode 100644
index 0000000000..d880d712c6
--- /dev/null
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/FetchableFluentQueryByPredicate.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2011-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.neo4j.repository.query;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.LongSupplier;
+import java.util.stream.Stream;
+
+import org.apiguardian.api.API;
+import org.neo4j.cypherdsl.core.Cypher;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.neo4j.core.FluentFindOperation;
+import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
+import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
+import org.springframework.data.support.PageableExecutionUtils;
+import org.springframework.lang.Nullable;
+
+import com.querydsl.core.types.Predicate;
+
+/**
+ * Immutable implementation of a {@link FetchableFluentQuery}. All
+ * methods that return a {@link FetchableFluentQuery} return a new instance, the original instance won't be
+ * modified.
+ *
+ * @author Michael J. Simons
+ * @param Source type
+ * @param Result type
+ * @since 6.2
+ * @soundtrack Die Ärzte - Geräusch
+ */
+@API(status = API.Status.INTERNAL, since = "6.2")
+final class FetchableFluentQueryByPredicate extends FluentQuerySupport implements FetchableFluentQuery {
+
+ private final Predicate predicate;
+
+ private final Neo4jPersistentEntity metaData;
+
+ private final FluentFindOperation findOperation;
+
+ private final Function countOperation;
+
+ private final Function existsOperation;
+
+ FetchableFluentQueryByPredicate(
+ Predicate predicate,
+ Neo4jPersistentEntity metaData,
+ Class resultType,
+ FluentFindOperation findOperation,
+ Function countOperation,
+ Function existsOperation
+ ) {
+ this(predicate, metaData, resultType, findOperation, countOperation, existsOperation, Sort.unsorted(), null);
+ }
+
+ FetchableFluentQueryByPredicate(
+ Predicate predicate,
+ Neo4jPersistentEntity metaData,
+ Class resultType,
+ FluentFindOperation findOperation,
+ Function countOperation,
+ Function existsOperation,
+ Sort sort,
+ @Nullable Collection properties
+ ) {
+ super(resultType, sort, properties);
+ this.predicate = predicate;
+ this.metaData = metaData;
+ this.findOperation = findOperation;
+ this.countOperation = countOperation;
+ this.existsOperation = existsOperation;
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public FetchableFluentQuery sortBy(Sort sort) {
+
+ return new FetchableFluentQueryByPredicate<>(this.predicate, this.metaData, this.resultType, this.findOperation,
+ this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public FetchableFluentQuery as(Class resultType) {
+
+ return new FetchableFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
+ this.countOperation, this.existsOperation);
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public FetchableFluentQuery project(Collection properties) {
+
+ return new FetchableFluentQueryByPredicate<>(this.predicate, this.metaData, this.resultType, this.findOperation,
+ this.countOperation, this.existsOperation, sort, mergeProperties(properties));
+ }
+
+ @Override
+ public R oneValue() {
+
+ return findOperation.find(metaData.getType())
+ .as(resultType)
+ .matching(
+ QueryFragmentsAndParameters.forCondition(metaData,
+ Cypher.adapt(predicate).asCondition(),
+ null,
+ CypherAdapterUtils.toSortItems(this.metaData, sort),
+ createIncludedFieldsPredicate()))
+ .oneValue();
+ }
+
+ @Override
+ public R firstValue() {
+
+ List all = all();
+ return all.isEmpty() ? null : all.get(0);
+ }
+
+ @Override
+ public List all() {
+
+ return findOperation.find(metaData.getType())
+ .as(resultType)
+ .matching(
+ QueryFragmentsAndParameters.forCondition(metaData,
+ Cypher.adapt(predicate).asCondition(),
+ null,
+ CypherAdapterUtils.toSortItems(this.metaData, sort),
+ createIncludedFieldsPredicate()))
+ .all();
+ }
+
+ @Override
+ public Page page(Pageable pageable) {
+
+ List page = findOperation.find(metaData.getType())
+ .as(resultType)
+ .matching(
+ QueryFragmentsAndParameters.forCondition(metaData,
+ Cypher.adapt(predicate).asCondition(),
+ pageable, null,
+ createIncludedFieldsPredicate()))
+ .all();
+
+ LongSupplier totalCountSupplier = this::count;
+ return PageableExecutionUtils.getPage(page, pageable, totalCountSupplier);
+ }
+
+ @Override
+ public Stream stream() {
+ return all().stream();
+ }
+
+ @Override
+ public long count() {
+ return countOperation.apply(predicate);
+ }
+
+ @Override
+ public boolean exists() {
+ return existsOperation.apply(predicate);
+ }
+}
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/FluentQuerySupport.java b/src/main/java/org/springframework/data/neo4j/repository/query/FluentQuerySupport.java
new file mode 100644
index 0000000000..f5bf64efeb
--- /dev/null
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/FluentQuerySupport.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.neo4j.repository.query;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.neo4j.core.mapping.PropertyFilter;
+import org.springframework.lang.Nullable;
+
+/**
+ * Supporting class containing some state and convenience methods for building fluent queries (both imperative and reactive).
+ *
+ * @author Michael J. Simons
+ * @param The result type
+ * @soundtrack Die Ärzte - Geräusch
+ */
+abstract class FluentQuerySupport {
+
+ protected final Class resultType;
+
+ protected final Sort sort;
+
+ @Nullable
+ protected final Set properties;
+
+ FluentQuerySupport(
+ Class resultType,
+ Sort sort,
+ @Nullable Collection properties
+ ) {
+ this.resultType = resultType;
+ this.sort = sort;
+ if (properties != null) {
+ this.properties = new HashSet<>(properties);
+ } else {
+ this.properties = null;
+ }
+ }
+
+ final Predicate createIncludedFieldsPredicate() {
+
+ if (this.properties == null) {
+ return path -> true;
+ }
+ return path -> this.properties.contains(path.toDotPath());
+ }
+
+ final Collection mergeProperties(Collection additionalProperties) {
+ Set newProperties = new HashSet<>();
+ if (this.properties != null) {
+ newProperties.addAll(this.properties);
+ }
+ newProperties.addAll(additionalProperties);
+ return Collections.unmodifiableCollection(newProperties);
+ }
+}
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java b/src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java
index 08de8bbb28..2cb7fed47f 100644
--- a/src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java
@@ -33,6 +33,7 @@
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.core.mapping.NodeDescription;
+import org.springframework.data.neo4j.core.mapping.PropertyFilter;
import org.springframework.lang.Nullable;
/**
@@ -124,27 +125,54 @@ public static QueryFragmentsAndParameters forFindAll(Neo4jPersistentEntity> en
* Following methods are used by the Simple(Reactive)QueryByExampleExecutor
*/
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example> example) {
- return QueryFragmentsAndParameters.forExample(mappingContext, example, null, null);
+ return QueryFragmentsAndParameters.forExample(mappingContext, example,
+ (java.util.function.Predicate) null);
+ }
+
+ static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example> example, @Nullable java.util.function.Predicate includeField) {
+ return QueryFragmentsAndParameters.forExample(mappingContext, example, null, null, includeField);
}
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example> example, Sort sort) {
- return QueryFragmentsAndParameters.forExample(mappingContext, example, null, sort);
+ return QueryFragmentsAndParameters.forExample(mappingContext, example, sort, null);
+ }
+
+ static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example> example, Sort sort, @Nullable java.util.function.Predicate includeField) {
+ return QueryFragmentsAndParameters.forExample(mappingContext, example, null, sort, includeField);
}
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example> example, Pageable pageable) {
return QueryFragmentsAndParameters.forExample(mappingContext, example, pageable, null);
}
+ static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example> example, Pageable pageable, @Nullable java.util.function.Predicate includeField) {
+ return QueryFragmentsAndParameters.forExample(mappingContext, example, pageable, null, includeField);
+ }
+
static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity> entityMetaData,
Condition condition,
@Nullable Pageable pageable,
@Nullable Collection sortItems
) {
+ return forCondition(entityMetaData, condition, pageable, sortItems, null);
+ }
+
+ static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity> entityMetaData,
+ Condition condition,
+ @Nullable Pageable pageable,
+ @Nullable Collection sortItems,
+ @Nullable java.util.function.Predicate includeField
+ ) {
QueryFragments queryFragments = new QueryFragments();
queryFragments.addMatchOn(cypherGenerator.createRootNode(entityMetaData));
queryFragments.setCondition(condition);
- queryFragments.setReturnExpressions(cypherGenerator.createReturnStatementForMatch(entityMetaData));
+ if (includeField == null) {
+ queryFragments.setReturnExpressions(cypherGenerator.createReturnStatementForMatch(entityMetaData));
+ } else {
+ queryFragments.setReturnExpressions(
+ cypherGenerator.createReturnStatementForMatch(entityMetaData, includeField));
+ }
queryFragments.setRenderConstantsAsParameters(true);
if (pageable != null) {
@@ -168,30 +196,36 @@ private static void adaptPageable(
}
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example> example,
- @Nullable Pageable pageable, @Nullable Sort sort) {
+ @Nullable Pageable pageable, @Nullable Sort sort, java.util.function.Predicate includeField) {
Predicate predicate = Predicate.create(mappingContext, example);
Map parameters = predicate.getParameters();
Condition condition = predicate.getCondition();
return getQueryFragmentsAndParameters(mappingContext.getPersistentEntity(example.getProbeType()), pageable,
- sort, parameters, condition);
+ sort, parameters, condition, includeField);
}
public static QueryFragmentsAndParameters forPageableAndSort(Neo4jPersistentEntity> neo4jPersistentEntity,
@Nullable Pageable pageable, @Nullable Sort sort) {
- return getQueryFragmentsAndParameters(neo4jPersistentEntity, pageable, sort, Collections.emptyMap(), null);
+ return getQueryFragmentsAndParameters(neo4jPersistentEntity, pageable, sort, Collections.emptyMap(), null, null);
}
private static QueryFragmentsAndParameters getQueryFragmentsAndParameters(
Neo4jPersistentEntity> entityMetaData, @Nullable Pageable pageable, @Nullable Sort sort,
- @Nullable Map parameters, @Nullable Condition condition) {
+ @Nullable Map parameters, @Nullable Condition condition, @Nullable
+ java.util.function.Predicate includeField) {
QueryFragments queryFragments = new QueryFragments();
queryFragments.addMatchOn(cypherGenerator.createRootNode(entityMetaData));
queryFragments.setCondition(condition);
- queryFragments.setReturnExpressions(cypherGenerator.createReturnStatementForMatch(entityMetaData));
+ if (includeField == null) {
+ queryFragments.setReturnExpressions(cypherGenerator.createReturnStatementForMatch(entityMetaData));
+ } else {
+ queryFragments.setReturnExpressions(
+ cypherGenerator.createReturnStatementForMatch(entityMetaData, includeField));
+ }
if (pageable != null) {
adaptPageable(entityMetaData, pageable, queryFragments);
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/QuerydslNeo4jPredicateExecutor.java b/src/main/java/org/springframework/data/neo4j/repository/query/QuerydslNeo4jPredicateExecutor.java
index 3a3d16bbf6..9cd5a48cca 100644
--- a/src/main/java/org/springframework/data/neo4j/repository/query/QuerydslNeo4jPredicateExecutor.java
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/QuerydslNeo4jPredicateExecutor.java
@@ -17,6 +17,7 @@
import java.util.Arrays;
import java.util.Optional;
+import java.util.function.Function;
import org.apiguardian.api.API;
import org.neo4j.cypherdsl.core.Cypher;
@@ -24,20 +25,22 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
+import org.springframework.data.neo4j.core.FluentFindOperation;
import org.springframework.data.neo4j.core.Neo4jOperations;
+import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.repository.support.CypherdslConditionExecutor;
import org.springframework.data.neo4j.repository.support.Neo4jEntityInformation;
-import org.springframework.data.neo4j.repository.support.SimpleNeo4jRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
/**
- * Querydsl specific fragment for extending {@link SimpleNeo4jRepository} with an implementation of {@link QuerydslPredicateExecutor}.
- * Provides the necessary infrastructure for translating Query-DSL predicates into conditions that are passed along
- * to the Cypher-DSL and eventually to the template infrastructure. This fragment will be loaded by the repository
- * infrastructure when
+ * Querydsl specific fragment for extending {@link org.springframework.data.neo4j.repository.support.SimpleNeo4jRepository}
+ * with an implementation of {@link QuerydslPredicateExecutor}. Provides the necessary infrastructure for translating
+ * Query-DSL predicates into conditions that are passed along to the Cypher-DSL and eventually to the template infrastructure.
+ * This fragment will be loaded by the repository infrastructure when a repository is declared extending the above interface.
*
* @author Michael J. Simons
* @param The returned domain type.
@@ -47,12 +50,27 @@
@API(status = API.Status.INTERNAL, since = "6.1")
public final class QuerydslNeo4jPredicateExecutor implements QuerydslPredicateExecutor {
+ /**
+ * Non-fluent operations are translated directly into Cypherdsl conditions and executed elsewhere.
+ */
private final CypherdslConditionExecutor delegate;
+ /**
+ * Needed to support the fluent operations.
+ */
+ private final Neo4jOperations neo4jOperations;
+
+ /**
+ * Needed to support the fluent operations.
+ */
+ private final Neo4jPersistentEntity metaData;
+
public QuerydslNeo4jPredicateExecutor(Neo4jEntityInformation entityInformation,
Neo4jOperations neo4jOperations) {
this.delegate = new CypherdslConditionExecutorImpl<>(entityInformation, neo4jOperations);
+ this.neo4jOperations = neo4jOperations;
+ this.metaData = entityInformation.getEntityMetaData();
}
@Override
@@ -74,15 +92,15 @@ public Iterable findAll(Predicate predicate, Sort sort) {
}
@Override
- public Iterable findAll(Predicate predicate, OrderSpecifier>... orderSpecifiers) {
+ public Iterable findAll(Predicate predicate, OrderSpecifier>... orders) {
- return this.delegate.findAll(Cypher.adapt(predicate).asCondition(), toSortItems(orderSpecifiers));
+ return this.delegate.findAll(Cypher.adapt(predicate).asCondition(), toSortItems(orders));
}
@Override
- public Iterable findAll(OrderSpecifier>... orderSpecifiers) {
+ public Iterable findAll(OrderSpecifier>... orders) {
- return this.delegate.findAll(toSortItems(orderSpecifiers));
+ return this.delegate.findAll(toSortItems(orders));
}
@Override
@@ -97,7 +115,7 @@ public long count(Predicate predicate) {
return this.delegate.count(Cypher.adapt(predicate).asCondition());
}
- private SortItem[] toSortItems(OrderSpecifier>... orderSpecifiers) {
+ static SortItem[] toSortItems(OrderSpecifier>... orderSpecifiers) {
return Arrays.stream(orderSpecifiers)
.map(os -> Cypher.sort(Cypher.adapt(os.getTarget()).asExpression(),
@@ -109,4 +127,18 @@ private SortItem[] toSortItems(OrderSpecifier>... orderSpecifiers) {
public boolean exists(Predicate predicate) {
return findAll(predicate).iterator().hasNext();
}
+
+ @Override
+ public R findBy(Predicate predicate, Function, R> queryFunction) {
+
+ if (this.neo4jOperations instanceof FluentFindOperation) {
+ @SuppressWarnings("unchecked") // defaultResultType will be a supertype of S and at this stage, the same.
+ FetchableFluentQuery fluentQuery =
+ (FetchableFluentQuery) new FetchableFluentQueryByPredicate<>(predicate, metaData, metaData.getType(),
+ (FluentFindOperation) this.neo4jOperations, this::count, this::exists);
+ return queryFunction.apply(fluentQuery);
+ }
+ throw new UnsupportedOperationException(
+ "Fluent find by predicate not supported with standard Neo4jOperations. Must support fluent queries too.");
+ }
}
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveFluentQueryByExample.java b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveFluentQueryByExample.java
new file mode 100644
index 0000000000..fa29f07be2
--- /dev/null
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveFluentQueryByExample.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2011-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.neo4j.repository.query;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collection;
+import java.util.function.Function;
+
+import org.apiguardian.api.API;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.neo4j.core.ReactiveFluentFindOperation;
+import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
+import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
+import org.springframework.data.support.PageableExecutionUtils;
+import org.springframework.lang.Nullable;
+
+/**
+ * Immutable implementation of a {@link ReactiveFluentQuery}. All
+ * methods that return a {@link ReactiveFluentQuery} return a new instance, the original instance won't be
+ * modified.
+ *
+ * @author Michael J. Simons
+ * @param Source type
+ * @param Result type
+ * @since 6.2
+ */
+@API(status = API.Status.INTERNAL, since = "6.2")
+final class ReactiveFluentQueryByExample extends FluentQuerySupport implements ReactiveFluentQuery {
+
+ private final Neo4jMappingContext mappingContext;
+
+ private final Example example;
+
+ private final ReactiveFluentFindOperation findOperation;
+
+ private final Function, Mono> countOperation;
+
+ private final Function, Mono> existsOperation;
+
+ ReactiveFluentQueryByExample(
+ Example example,
+ Class resultType,
+ Neo4jMappingContext mappingContext,
+ ReactiveFluentFindOperation findOperation,
+ Function, Mono> countOperation,
+ Function, Mono> existsOperation
+ ) {
+ this(example, resultType, mappingContext, findOperation, countOperation, existsOperation, Sort.unsorted(),
+ null);
+ }
+
+ ReactiveFluentQueryByExample(
+ Example example,
+ Class resultType,
+ Neo4jMappingContext mappingContext,
+ ReactiveFluentFindOperation findOperation,
+ Function, Mono> countOperation,
+ Function, Mono> existsOperation,
+ Sort sort,
+ @Nullable Collection properties
+ ) {
+ super(resultType, sort, properties);
+ this.mappingContext = mappingContext;
+ this.example = example;
+ this.findOperation = findOperation;
+ this.countOperation = countOperation;
+ this.existsOperation = existsOperation;
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public ReactiveFluentQuery sortBy(Sort sort) {
+
+ return new ReactiveFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
+ this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public ReactiveFluentQuery as(Class resultType) {
+
+ return new ReactiveFluentQueryByExample<>(this.example, resultType, this.mappingContext, this.findOperation,
+ this.countOperation, this.existsOperation);
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public ReactiveFluentQuery project(Collection properties) {
+
+ return new ReactiveFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
+ this.countOperation, this.existsOperation, sort, mergeProperties(properties));
+ }
+
+ @Override
+ public Mono one() {
+
+ return findOperation.find(example.getProbeType())
+ .as(resultType)
+ .matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
+ createIncludedFieldsPredicate()))
+ .one();
+ }
+
+ @Override
+ public Mono first() {
+
+ return all().take(1).singleOrEmpty();
+ }
+
+ @Override
+ public Flux all() {
+
+ return findOperation.find(example.getProbeType())
+ .as(resultType)
+ .matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
+ createIncludedFieldsPredicate()))
+ .all();
+ }
+
+ @Override
+ public Mono> page(Pageable pageable) {
+
+ Flux results = findOperation.find(example.getProbeType())
+ .as(resultType)
+ .matching(QueryFragmentsAndParameters.forExample(mappingContext, example, pageable,
+ createIncludedFieldsPredicate()))
+ .all();
+ return results.collectList().zipWith(countOperation.apply(example)).map(tuple -> {
+ Page page = PageableExecutionUtils.getPage(tuple.getT1(), pageable, () -> tuple.getT2());
+ return page;
+ });
+ }
+
+ @Override
+ public Mono count() {
+ return countOperation.apply(example);
+ }
+
+ @Override
+ public Mono exists() {
+ return existsOperation.apply(example);
+ }
+}
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveFluentQueryByPredicate.java b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveFluentQueryByPredicate.java
new file mode 100644
index 0000000000..dfa03d3684
--- /dev/null
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveFluentQueryByPredicate.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2011-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.neo4j.repository.query;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collection;
+import java.util.function.Function;
+
+import org.apiguardian.api.API;
+import org.neo4j.cypherdsl.core.Cypher;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.neo4j.core.ReactiveFluentFindOperation;
+import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
+import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
+import org.springframework.data.support.PageableExecutionUtils;
+import org.springframework.lang.Nullable;
+
+import com.querydsl.core.types.Predicate;
+
+/**
+ * Immutable implementation of a {@link ReactiveFluentQuery}. All
+ * methods that return a {@link ReactiveFluentQuery} return a new instance, the original instance won't be
+ * modified.
+ *
+ * @author Michael J. Simons
+ * @param Source type
+ * @param Result type
+ * @since 6.2
+ */
+@API(status = API.Status.INTERNAL, since = "6.2") final class ReactiveFluentQueryByPredicate
+ extends FluentQuerySupport implements ReactiveFluentQuery {
+
+ private final Predicate predicate;
+
+ private final Neo4jPersistentEntity metaData;
+
+ private final ReactiveFluentFindOperation findOperation;
+
+ private final Function> countOperation;
+
+ private final Function> existsOperation;
+
+ ReactiveFluentQueryByPredicate(
+ Predicate predicate,
+ Neo4jPersistentEntity metaData,
+ Class resultType,
+ ReactiveFluentFindOperation findOperation,
+ Function> countOperation,
+ Function> existsOperation
+ ) {
+ this(predicate, metaData, resultType, findOperation, countOperation, existsOperation, Sort.unsorted(), null);
+ }
+
+ ReactiveFluentQueryByPredicate(
+ Predicate predicate,
+ Neo4jPersistentEntity metaData,
+ Class resultType,
+ ReactiveFluentFindOperation findOperation,
+ Function> countOperation,
+ Function> existsOperation,
+ Sort sort,
+ @Nullable Collection properties
+ ) {
+ super(resultType, sort, properties);
+ this.predicate = predicate;
+ this.metaData = metaData;
+ this.findOperation = findOperation;
+ this.countOperation = countOperation;
+ this.existsOperation = existsOperation;
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public ReactiveFluentQuery sortBy(Sort sort) {
+
+ return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, this.resultType, this.findOperation,
+ this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public ReactiveFluentQuery as(Class resultType) {
+
+ return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
+ this.countOperation, this.existsOperation);
+ }
+
+ @Override
+ @SuppressWarnings("HiddenField")
+ public ReactiveFluentQuery project(Collection properties) {
+
+ return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
+ this.countOperation, this.existsOperation, sort, mergeProperties(properties));
+ }
+
+ @Override
+ public Mono one() {
+
+ return findOperation.find(metaData.getType())
+ .as(resultType)
+ .matching(
+ QueryFragmentsAndParameters.forCondition(metaData,
+ Cypher.adapt(predicate).asCondition(),
+ null,
+ CypherAdapterUtils.toSortItems(this.metaData, sort),
+ createIncludedFieldsPredicate()))
+ .one();
+ }
+
+ @Override
+ public Mono first() {
+
+ return all().take(1).singleOrEmpty();
+ }
+
+ @Override
+ public Flux all() {
+
+ return findOperation.find(metaData.getType())
+ .as(resultType)
+ .matching(
+ QueryFragmentsAndParameters.forCondition(metaData,
+ Cypher.adapt(predicate).asCondition(),
+ null,
+ CypherAdapterUtils.toSortItems(this.metaData, sort),
+ createIncludedFieldsPredicate()))
+ .all();
+ }
+
+ @Override
+ public Mono> page(Pageable pageable) {
+
+ Flux results = findOperation.find(metaData.getType())
+ .as(resultType)
+ .matching(
+ QueryFragmentsAndParameters.forCondition(metaData,
+ Cypher.adapt(predicate).asCondition(),
+ pageable, null,
+ createIncludedFieldsPredicate()))
+ .all();
+
+ return results.collectList().zipWith(countOperation.apply(predicate)).map(tuple -> {
+ Page page = PageableExecutionUtils.getPage(tuple.getT1(), pageable, () -> tuple.getT2());
+ return page;
+ });
+ }
+
+ @Override
+ public Mono count() {
+ return countOperation.apply(predicate);
+ }
+
+ @Override
+ public Mono exists() {
+ return existsOperation.apply(predicate);
+ }
+}
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveQuerydslNeo4jPredicateExecutor.java b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveQuerydslNeo4jPredicateExecutor.java
new file mode 100644
index 0000000000..aa1fbef240
--- /dev/null
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveQuerydslNeo4jPredicateExecutor.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2011-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.neo4j.repository.query;
+
+import static org.neo4j.cypherdsl.core.Cypher.asterisk;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.function.Function;
+
+import org.apiguardian.api.API;
+import org.neo4j.cypherdsl.core.Condition;
+import org.neo4j.cypherdsl.core.Conditions;
+import org.neo4j.cypherdsl.core.Cypher;
+import org.neo4j.cypherdsl.core.Functions;
+import org.neo4j.cypherdsl.core.SortItem;
+import org.neo4j.cypherdsl.core.Statement;
+import org.reactivestreams.Publisher;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.neo4j.core.ReactiveFluentFindOperation;
+import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
+import org.springframework.data.neo4j.core.mapping.CypherGenerator;
+import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
+import org.springframework.data.neo4j.repository.support.Neo4jEntityInformation;
+import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
+import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
+
+import com.querydsl.core.types.OrderSpecifier;
+import com.querydsl.core.types.Predicate;
+
+/**
+ * Querydsl specific fragment for extending {@link org.springframework.data.neo4j.repository.support.SimpleReactiveNeo4jRepository}
+ * with an implementation of {@link ReactiveQuerydslPredicateExecutor}. Provides the necessary infrastructure for translating
+ * Query-DSL predicates into conditions that are passed along to the Cypher-DSL and eventually to the template infrastructure.
+ * This fragment will be loaded by the repository infrastructure when a repository is declared extending the above interface.
+ *
+ * @author Michael J. Simons
+ * @param The returned domain type.
+ * @since 6.2
+ */
+@API(status = API.Status.INTERNAL, since = "6.2")
+public final class ReactiveQuerydslNeo4jPredicateExecutor implements ReactiveQuerydslPredicateExecutor {
+
+ private final Neo4jEntityInformation entityInformation;
+
+ private final ReactiveNeo4jOperations neo4jOperations;
+
+ private final Neo4jPersistentEntity metaData;
+
+ public ReactiveQuerydslNeo4jPredicateExecutor(Neo4jEntityInformation entityInformation,
+ ReactiveNeo4jOperations neo4jOperations) {
+
+ this.entityInformation = entityInformation;
+ this.neo4jOperations = neo4jOperations;
+ this.metaData = this.entityInformation.getEntityMetaData();
+ }
+
+ @Override
+ public Mono findOne(Predicate predicate) {
+
+ return this.neo4jOperations.toExecutableQuery(
+ this.metaData.getType(),
+ QueryFragmentsAndParameters.forCondition(this.metaData, Cypher.adapt(predicate).asCondition(), null,
+ null)
+ ).flatMap(ReactiveNeo4jOperations.ExecutableQuery::getSingleResult);
+ }
+
+ @Override
+ public Flux findAll(Predicate predicate) {
+
+ return doFindAll(Cypher.adapt(predicate).asCondition(), null);
+ }
+
+ @Override
+ public Flux findAll(Predicate predicate, Sort sort) {
+
+ return doFindAll(Cypher.adapt(predicate).asCondition(), CypherAdapterUtils.toSortItems(this.metaData, sort));
+ }
+
+ @Override
+ public Flux findAll(Predicate predicate, OrderSpecifier>... orders) {
+ return doFindAll(Cypher.adapt(predicate).asCondition(), Arrays.asList(QuerydslNeo4jPredicateExecutor.toSortItems(orders)));
+
+ }
+
+ @Override
+ public Flux findAll(OrderSpecifier>... orders) {
+
+ return doFindAll(Conditions.noCondition(), Arrays.asList(QuerydslNeo4jPredicateExecutor.toSortItems(orders)));
+ }
+
+ private Flux doFindAll(Condition condition, Collection sortItems) {
+ return this.neo4jOperations.toExecutableQuery(
+ this.metaData.getType(),
+ QueryFragmentsAndParameters.forCondition(this.metaData, condition, null,
+ sortItems)
+ ).flatMapMany(ReactiveNeo4jOperations.ExecutableQuery::getResults);
+ }
+
+ @Override
+ public Mono count(Predicate predicate) {
+
+ Statement statement = CypherGenerator.INSTANCE.prepareMatchOf(this.metaData,
+ Cypher.adapt(predicate).asCondition())
+ .returning(Functions.count(asterisk())).build();
+ return this.neo4jOperations.count(statement, statement.getParameters());
+ }
+
+ @Override
+ public Mono exists(Predicate predicate) {
+ return findAll(predicate).hasElements();
+ }
+
+ @Override
+ public > P findBy(Predicate predicate, Function, P> queryFunction) {
+
+ if (this.neo4jOperations instanceof ReactiveFluentFindOperation) {
+ @SuppressWarnings("unchecked") // defaultResultType will be a supertype of S and at this stage, the same.
+ ReactiveFluentQuery fluentQuery = (ReactiveFluentQuery) new ReactiveFluentQueryByPredicate<>(predicate, metaData, metaData.getType(),
+ (ReactiveFluentFindOperation) this.neo4jOperations, this::count, this::exists);
+ return queryFunction.apply(fluentQuery);
+ }
+ throw new UnsupportedOperationException(
+ "Fluent find by example not supported with standard Neo4jOperations. Must support fluent queries too.");
+ }
+}
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/SimpleQueryByExampleExecutor.java b/src/main/java/org/springframework/data/neo4j/repository/query/SimpleQueryByExampleExecutor.java
index c9e6924a76..68e6ce49d9 100644
--- a/src/main/java/org/springframework/data/neo4j/repository/query/SimpleQueryByExampleExecutor.java
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/SimpleQueryByExampleExecutor.java
@@ -22,14 +22,17 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
+import org.springframework.data.neo4j.core.FluentFindOperation;
import org.springframework.data.neo4j.core.Neo4jOperations;
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
+import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.data.support.PageableExecutionUtils;
import java.util.List;
import java.util.Optional;
+import java.util.function.Function;
import java.util.function.LongSupplier;
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
@@ -101,4 +104,15 @@ public boolean exists(Example example) {
return findAll(example).iterator().hasNext();
}
+ @Override
+ public R findBy(Example example, Function, R> queryFunction) {
+
+ if (this.neo4jOperations instanceof FluentFindOperation) {
+ FetchableFluentQuery fluentQuery = new FetchableFluentQueryByExample<>(example, example.getProbeType(),
+ mappingContext, (FluentFindOperation) this.neo4jOperations, this::count, this::exists);
+ return queryFunction.apply(fluentQuery);
+ }
+ throw new UnsupportedOperationException(
+ "Fluent find by example not supported with standard Neo4jOperations. Must support fluent queries too.");
+ }
}
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/SimpleReactiveQueryByExampleExecutor.java b/src/main/java/org/springframework/data/neo4j/repository/query/SimpleReactiveQueryByExampleExecutor.java
index 2bc08ec6ad..1aac18cf05 100644
--- a/src/main/java/org/springframework/data/neo4j/repository/query/SimpleReactiveQueryByExampleExecutor.java
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/SimpleReactiveQueryByExampleExecutor.java
@@ -18,17 +18,22 @@
import org.apiguardian.api.API;
import org.neo4j.cypherdsl.core.Functions;
import org.neo4j.cypherdsl.core.Statement;
+import org.reactivestreams.Publisher;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
+import org.springframework.data.neo4j.core.ReactiveFluentFindOperation;
import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
+import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
+import java.util.function.Function;
+
/**
* A fragment for repositories providing "Query by example" functionality in a reactive way.
*
@@ -88,4 +93,15 @@ public Mono