DATAJPA-1235 - Polishing.

Original pull request: #244.
This commit is contained in:
Oliver Gierke
2018-01-24 11:52:41 +01:00
parent 2473555183
commit c9fcca5f32
7 changed files with 125 additions and 106 deletions

View File

@@ -24,6 +24,7 @@ import org.springframework.util.StringUtils;
* A wrapper for a String representation of a query offering information about the query.
*
* @author Jens Schauder
* @since 2.0.3
*/
interface DeclaredQuery {
@@ -31,7 +32,6 @@ interface DeclaredQuery {
* Creates a {@literal DeclaredQuery} from a query {@literal String}.
*
* @param query might be {@literal null} or empty.
*
* @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument.
*/
static DeclaredQuery of(@Nullable String query) {
@@ -80,8 +80,7 @@ interface DeclaredQuery {
*
* @param countQuery an optional query string to be used if present.
* @param countQueryProjection an optional return type for the query.
* @return A new {@literal DeclaredQuery} instance.
* @return a new {@literal DeclaredQuery} instance.
*/
DeclaredQuery deriveCountQuery(@Nullable String countQuery, @Nullable String countQueryProjection);
}

View File

@@ -15,16 +15,17 @@
*/
package org.springframework.data.jpa.repository.query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.List;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* NULL-Object pattern implementation.
* NULL-Object pattern implementation for {@link DeclaredQuery}.
*
* @author Jens Schauder
* @since 2.0.3
*/
class EmptyDeclaredQuery implements DeclaredQuery {
@@ -33,36 +34,64 @@ class EmptyDeclaredQuery implements DeclaredQuery {
*/
static final DeclaredQuery EMPTY_QUERY = new EmptyDeclaredQuery();
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#hasNamedParameter()
*/
@Override
public boolean hasNamedParameter() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#getQueryString()
*/
@Override
public String getQueryString() {
return "";
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#getAlias()
*/
@Override
public String getAlias() {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#hasConstructorExpression()
*/
@Override
public boolean hasConstructorExpression() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#isDefaultProjection()
*/
@Override
public boolean isDefaultProjection() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#getParameterBindings()
*/
@Override
public List<StringQuery.ParameterBinding> getParameterBindings() {
return Collections.emptyList();
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#deriveCountQuery(java.lang.String, java.lang.String)
*/
@Override
public DeclaredQuery deriveCountQuery(@Nullable String countQuery, @Nullable String countQueryProjection) {

View File

@@ -124,7 +124,7 @@ class ParameterBinderFactory {
}
private static Iterable<QueryParameterSetter> createSetters(List<ParameterBinding> parameterBindings,
DeclaredQuery declaredQuery, QueryParameterSetterFactory... strategies) {
DeclaredQuery declaredQuery, QueryParameterSetterFactory... strategies) {
return parameterBindings.stream() //
.map(it -> createQueryParameterSetter(it, strategies, declaredQuery)) //

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2018 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
*
* http://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.jpa.repository.query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.data.domain.Range;
import org.springframework.lang.Nullable;
/**
* Datastructure that analyses a String to determine the parts of the String that are quoted and offers an API to query
* that information.
*
* @author Jens Schauder
*/
class QuotationMap {
private static final Set<Character> QUOTING_CHARACTERS = new HashSet<>(Arrays.asList('"', '\''));
private List<Range<Integer>> quotedRanges = new ArrayList<>();
QuotationMap(@Nullable String query) {
if (query == null)
return;
Character inQuotation = null;
int start = 0;
for (int i = 0; i < query.length(); i++) {
char currentChar = query.charAt(i);
if (QUOTING_CHARACTERS.contains(currentChar)) {
if (inQuotation == null) {
inQuotation = currentChar;
start = i;
} else if (currentChar == inQuotation) {
inQuotation = null;
quotedRanges.add(Range.of(Range.Bound.inclusive(start), Range.Bound.inclusive(i)));
}
}
}
if (inQuotation != null) {
throw new IllegalArgumentException(
String.format("The string <%s> starts a quoted range at %d, but never ends it.", query, start));
}
}
/**
* @param index to check if it is part of a quoted range.
* @return whether the query contains a quoted range at {@literal index}.
*/
public boolean isQuoted(int index) {
return quotedRanges.stream().anyMatch(r -> r.contains(index));
}
}

View File

@@ -22,10 +22,13 @@ import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.data.domain.Range;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -75,13 +78,23 @@ class StringQuery implements DeclaredQuery {
return !bindings.isEmpty();
}
/**
* Returns the {@link ParameterBinding}s registered.
String getProjection() {
return QueryUtils.getProjection(query);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#getParameterBindings()
*/
@Override
public List<ParameterBinding> getParameterBindings() {
return bindings;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#deriveCountQuery(java.lang.String, java.lang.String)
*/
@Override
public DeclaredQuery deriveCountQuery(@Nullable String countQuery, @Nullable String countQueryProjection) {
@@ -89,18 +102,18 @@ class StringQuery implements DeclaredQuery {
.of(countQuery != null ? countQuery : QueryUtils.createCountQueryFor(query, countQueryProjection));
}
/**
* Returns the query string.
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#getQueryString()
*/
@Override
public String getQueryString() {
return query;
}
/**
* Returns the main alias used in the query.
*
* @return the alias
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#getAlias()
*/
@Override
@Nullable
@@ -108,33 +121,31 @@ class StringQuery implements DeclaredQuery {
return alias;
}
/**
* Returns whether the query is using a constructor expression.
*
* @since 1.10
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#hasConstructorExpression()
*/
@Override
public boolean hasConstructorExpression() {
return hasConstructorExpression;
}
/**
* Returns whether the query uses the default projection, i.e. returns the main alias defined for the query.
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#isDefaultProjection()
*/
@Override
public boolean isDefaultProjection() {
return getProjection().equals(alias);
}
public String getProjection() {
return QueryUtils.getProjection(query);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.DeclaredQuery#hasNamedParameter()
*/
@Override
public boolean hasNamedParameter() {
return bindings.stream() //
.anyMatch(b -> b.getName() != null);
return bindings.stream().anyMatch(b -> b.getName() != null);
}
/**
@@ -760,4 +771,59 @@ class StringQuery implements DeclaredQuery {
}
}
/**
* Value object to analyze a String to determine the parts of the String that are quoted and offers an API to query
* that information.
*
* @author Jens Schauder
* @since 3.0.3
*/
static class QuotationMap {
private static final Set<Character> QUOTING_CHARACTERS = new HashSet<>(Arrays.asList('"', '\''));
private List<Range<Integer>> quotedRanges = new ArrayList<>();
QuotationMap(@Nullable String query) {
if (query == null) {
return;
}
Character inQuotation = null;
int start = 0;
for (int i = 0; i < query.length(); i++) {
char currentChar = query.charAt(i);
if (QUOTING_CHARACTERS.contains(currentChar)) {
if (inQuotation == null) {
inQuotation = currentChar;
start = i;
} else if (currentChar == inQuotation) {
inQuotation = null;
quotedRanges.add(Range.of(Range.Bound.inclusive(start), Range.Bound.inclusive(i)));
}
}
}
if (inQuotation != null) {
throw new IllegalArgumentException(
String.format("The string <%s> starts a quoted range at %d, but never ends it.", query, start));
}
}
/**
* @param index to check if it is part of a quoted range.
* @return whether the query contains a quoted range at {@literal index}.
*/
public boolean isQuoted(int index) {
return quotedRanges.stream().anyMatch(r -> r.contains(index));
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Query implementation to exectue queries against JPA.
* Query implementation to execute queries against JPA.
*/
@org.springframework.lang.NonNullApi
package org.springframework.data.jpa.repository.query;

View File

@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import org.springframework.data.jpa.repository.query.StringQuery.QuotationMap;
/**
* Unit tests for {@link QuotationMap}.