Skip to content

Add FluentQuery support to QuerydslPredicateExecutor and QueryByExampleExecutor. #2360

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

Closed
wants to merge 4 commits into from
Closed
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
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>6.2.0-SNAPSHOT</version>
<version>6.2.0-2343-SNAPSHOT</version>

<name>Spring Data Neo4j</name>
<description>Next generation Object-Graph-Mapping for Spring Data.</description>
Expand Down Expand Up @@ -117,7 +117,7 @@
<skipIntegrationTests>${skipTests}</skipIntegrationTests>

<skipUnitTests>${skipTests}</skipUnitTests>
<springdata.commons>2.6.0-SNAPSHOT</springdata.commons>
<springdata.commons>2.6.0-2228-SNAPSHOT</springdata.commons>

<spring-data-commons-docs.dir>../../../../spring-data-commons/src/main/asciidoc</spring-data-commons-docs.dir>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -106,6 +107,16 @@ interface FindWithQuery<T> extends TerminatingFindWithoutQuery<T> {
*/
TerminatingFind<T> matching(String query, @Nullable Map<String, Object> 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<T> matching(QueryFragmentsAndParameters queryFragmentsAndParameters);

/**
* Set the filter query to be used.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -51,6 +52,7 @@ private static class ExecutableFindSupport<T>
private final Class<T> returnType;
private final String query;
private final Map<String, Object> parameters;
private final QueryFragmentsAndParameters queryFragmentsAndParameters;

ExecutableFindSupport(Neo4jTemplate template, Class<?> domainType, Class<T> returnType, String query,
Map<String, Object> parameters) {
Expand All @@ -59,6 +61,16 @@ private static class ExecutableFindSupport<T>
this.returnType = returnType;
this.query = query;
this.parameters = parameters;
this.queryFragmentsAndParameters = null;
}

ExecutableFindSupport(Neo4jTemplate template, Class<?> domainType, Class<T> returnType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
this.template = template;
this.domainType = domainType;
this.returnType = returnType;
this.query = null;
this.parameters = null;
this.queryFragmentsAndParameters = queryFragmentsAndParameters;
}

@Override
Expand All @@ -79,6 +91,15 @@ public TerminatingFind<T> matching(String query, Map<String, Object> parameters)
return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters);
}

@Override
@SuppressWarnings("HiddenField")
public TerminatingFind<T> matching(QueryFragmentsAndParameters queryFragmentsAndParameters) {

Assert.notNull(queryFragmentsAndParameters, "Query fragments must not be null!");

return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters);
}

@Override
public T oneValue() {

Expand All @@ -95,7 +116,7 @@ public List<T> all() {
}

private List<T> doFind(TemplateSupport.FetchType fetchType) {
return template.doFind(query, parameters, domainType, returnType, fetchType);
return template.doFind(query, parameters, domainType, returnType, fetchType, queryFragmentsAndParameters);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,19 @@ public <T> ExecutableFind<T> find(Class<T> domainType) {
}

@SuppressWarnings("unchecked")
<T, R> List<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType) {
<T, R> List<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType, @Nullable QueryFragmentsAndParameters queryFragmentsAndParameters) {

List<T> 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<T> executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
parameters == null ? Collections.emptyMap() : parameters);
ExecutableQuery<T> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -96,6 +97,16 @@ interface FindWithQuery<T> extends TerminatingFindWithoutQuery<T> {
*/
TerminatingFind<T> matching(String query, @Nullable Map<String, Object> 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<T> matching(QueryFragmentsAndParameters queryFragmentsAndParameters);

/**
* Set the filter query to be used.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -54,6 +55,7 @@ private static class ExecutableFindSupport<T>
private final Class<T> returnType;
private final String query;
private final Map<String, Object> parameters;
private final QueryFragmentsAndParameters queryFragmentsAndParameters;

ExecutableFindSupport(ReactiveNeo4jTemplate template, Class<?> domainType, Class<T> returnType, String query,
Map<String, Object> parameters) {
Expand All @@ -62,6 +64,16 @@ private static class ExecutableFindSupport<T>
this.returnType = returnType;
this.query = query;
this.parameters = parameters;
this.queryFragmentsAndParameters = null;
}

ExecutableFindSupport(ReactiveNeo4jTemplate template, Class<?> domainType, Class<T> returnType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
this.template = template;
this.domainType = domainType;
this.returnType = returnType;
this.query = null;
this.parameters = null;
this.queryFragmentsAndParameters = queryFragmentsAndParameters;
}

@Override
Expand All @@ -82,6 +94,13 @@ public TerminatingFind<T> matching(String query, Map<String, Object> parameters)
return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters);
}

@Override
@SuppressWarnings("HiddenField")
public TerminatingFind<T> matching(QueryFragmentsAndParameters queryFragmentsAndParameters) {

return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters);
}

@Override
public Mono<T> one() {
return doFind(TemplateSupport.FetchType.ONE).single();
Expand All @@ -93,7 +112,7 @@ public Flux<T> all() {
}

private Flux<T> doFind(TemplateSupport.FetchType fetchType) {
return template.doFind(query, parameters, domainType, returnType, fetchType);
return template.doFind(query, parameters, domainType, returnType, fetchType, queryFragmentsAndParameters);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,14 +225,19 @@ public <T> ExecutableFind<T> find(Class<T> domainType) {
}

@SuppressWarnings("unchecked")
<T, R> Flux<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType) {
<T, R> Flux<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType, @Nullable QueryFragmentsAndParameters queryFragmentsAndParameters) {

Flux<T> 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<T>> executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
parameters == null ? Collections.emptyMap() : parameters);
Mono<ExecutableQuery<T>> executableQuery;
if (queryFragmentsAndParameters == null) {
executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
parameters == null ? Collections.emptyMap() : parameters);
} else {
executableQuery = createExecutableQuery(domainType, resultType, queryFragmentsAndParameters);
}

switch (fetchType) {
case ALL:
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <S> Source type
* @param <R> Result type
* @since 6.2
*/
@API(status = API.Status.INTERNAL, since = "6.2")
final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> implements FetchableFluentQuery<R> {

private final Neo4jMappingContext mappingContext;

private final Example<S> example;

private final FluentFindOperation findOperation;

private final Function<Example<S>, Long> countOperation;

private final Function<Example<S>, Boolean> existsOperation;

FetchableFluentQueryByExample(
Example<S> example,
Class<R> resultType,
Neo4jMappingContext mappingContext,
FluentFindOperation findOperation,
Function<Example<S>, Long> countOperation,
Function<Example<S>, Boolean> existsOperation
) {
this(example, resultType, mappingContext, findOperation, countOperation, existsOperation, Sort.unsorted(),
null);
}

FetchableFluentQueryByExample(
Example<S> example,
Class<R> resultType,
Neo4jMappingContext mappingContext,
FluentFindOperation findOperation,
Function<Example<S>, Long> countOperation,
Function<Example<S>, Boolean> existsOperation,
Sort sort,
@Nullable Collection<String> 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<R> 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 <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {

return new FetchableFluentQueryByExample<>(this.example, resultType, this.mappingContext, this.findOperation,
this.countOperation, this.existsOperation);
}

@Override
@SuppressWarnings("HiddenField")
public FetchableFluentQuery<R> project(Collection<String> 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<R> all = all();
return all.isEmpty() ? null : all.get(0);
}

@Override
public List<R> all() {

return findOperation.find(example.getProbeType())
.as(resultType)
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
createIncludedFieldsPredicate()))
.all();
}

@Override
public Page<R> page(Pageable pageable) {

List<R> 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<R> stream() {
return all().stream();
}

@Override
public long count() {
return countOperation.apply(example);
}

@Override
public boolean exists() {
return existsOperation.apply(example);
}
}
Loading