DATAJDBC-513 - Introduce Query, Criteria and Update Objects for Spring Data Relational.

This commit is contained in:
Mark Paluch
2020-03-27 09:29:31 +01:00
parent beaeb5cbdf
commit 7802f6e19e
7 changed files with 1527 additions and 0 deletions

View File

@@ -0,0 +1,695 @@
/*
* Copyright 2019-2020 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.relational.core.query;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Central class for creating queries. It follows a fluent API style so that you can easily chain together multiple
* criteria. Static import of the {@code Criteria.property(…)} method will improve readability as in
* {@code where(property(…).is(…)}.
* <p>
* The Criteria API supports composition with a {@link #empty() NULL object} and a {@link #from(List) static factory
* method}. Example usage:
*
* <pre class="code">
* Criteria.from(Criteria.where("name").is("Foo"), Criteria.from(Criteria.where("age").greaterThan(42)));
* </pre>
*
* rendering:
*
* <pre class="code">
* WHERE name = 'Foo' AND age > 42
* </pre>
*
* @author Mark Paluch
* @author Oliver Drotbohm
* @author Roman Chigvintsev
* @since 2.0
*/
public class Criteria implements CriteriaDefinition {
static final Criteria EMPTY = new Criteria(SqlIdentifier.EMPTY, Comparator.INITIAL, null);
private final @Nullable Criteria previous;
private final Combinator combinator;
private final List<Criteria> group;
private final @Nullable SqlIdentifier column;
private final @Nullable Comparator comparator;
private final @Nullable Object value;
private final boolean ignoreCase;
private Criteria(SqlIdentifier column, Comparator comparator, @Nullable Object value) {
this(null, Combinator.INITIAL, Collections.emptyList(), column, comparator, value, false);
}
private Criteria(@Nullable Criteria previous, Combinator combinator, List<Criteria> group,
@Nullable SqlIdentifier column, @Nullable Comparator comparator, @Nullable Object value) {
this(previous, combinator, group, column, comparator, value, false);
}
private Criteria(@Nullable Criteria previous, Combinator combinator, List<Criteria> group,
@Nullable SqlIdentifier column, @Nullable Comparator comparator, @Nullable Object value, boolean ignoreCase) {
this.previous = previous;
this.combinator = previous != null && previous.isEmpty() ? Combinator.INITIAL : combinator;
this.group = group;
this.column = column;
this.comparator = comparator;
this.value = value;
this.ignoreCase = ignoreCase;
}
private Criteria(@Nullable Criteria previous, Combinator combinator, List<Criteria> group) {
this.previous = previous;
this.combinator = previous != null && previous.isEmpty() ? Combinator.INITIAL : combinator;
this.group = group;
this.column = null;
this.comparator = null;
this.value = null;
this.ignoreCase = false;
}
/**
* Static factory method to create an empty Criteria.
*
* @return an empty {@link Criteria}.
*/
public static Criteria empty() {
return EMPTY;
}
/**
* Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link List Criterias}.
*
* @return new {@link Criteria}.
*/
public static Criteria from(Criteria... criteria) {
Assert.notNull(criteria, "Criteria must not be null");
Assert.noNullElements(criteria, "Criteria must not contain null elements");
return from(Arrays.asList(criteria));
}
/**
* Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link List Criterias}.
*
* @return new {@link Criteria}.
*/
public static Criteria from(List<Criteria> criteria) {
Assert.notNull(criteria, "Criteria must not be null");
Assert.noNullElements(criteria, "Criteria must not contain null elements");
if (criteria.isEmpty()) {
return EMPTY;
}
if (criteria.size() == 1) {
return criteria.get(0);
}
return EMPTY.and(criteria);
}
/**
* Static factory method to create a Criteria using the provided {@code column} name.
*
* @param column Must not be {@literal null} or empty.
* @return a new {@link CriteriaStep} object to complete the first {@link Criteria}.
*/
public static CriteriaStep where(String column) {
Assert.hasText(column, "Column name must not be null or empty!");
return new DefaultCriteriaStep(SqlIdentifier.unquoted(column));
}
/**
* Create a new {@link Criteria} and combine it with {@code AND} using the provided {@code column} name.
*
* @param column Must not be {@literal null} or empty.
* @return a new {@link CriteriaStep} object to complete the next {@link Criteria}.
*/
public CriteriaStep and(String column) {
Assert.hasText(column, "Column name must not be null or empty!");
SqlIdentifier identifier = SqlIdentifier.unquoted(column);
return new DefaultCriteriaStep(identifier) {
@Override
protected Criteria createCriteria(Comparator comparator, Object value) {
return new Criteria(Criteria.this, Combinator.AND, Collections.emptyList(), identifier, comparator, value);
}
};
}
/**
* Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link Criteria} group.
*
* @param criteria criteria object.
* @return a new {@link Criteria} object.
* @since 1.1
*/
public Criteria and(Criteria criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
return and(Collections.singletonList(criteria));
}
/**
* Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link Criteria} group.
*
* @param criteria criteria objects.
* @return a new {@link Criteria} object.
* @since 1.1
*/
public Criteria and(List<Criteria> criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
return new Criteria(Criteria.this, Combinator.AND, criteria);
}
/**
* Create a new {@link Criteria} and combine it with {@code OR} using the provided {@code column} name.
*
* @param column Must not be {@literal null} or empty.
* @return a new {@link CriteriaStep} object to complete the next {@link Criteria}.
*/
public CriteriaStep or(String column) {
Assert.hasText(column, "Column name must not be null or empty!");
SqlIdentifier identifier = SqlIdentifier.unquoted(column);
return new DefaultCriteriaStep(identifier) {
@Override
protected Criteria createCriteria(Comparator comparator, Object value) {
return new Criteria(Criteria.this, Combinator.OR, Collections.emptyList(), identifier, comparator, value);
}
};
}
/**
* Create a new {@link Criteria} and combine it as group with {@code OR} using the provided {@link Criteria} group.
*
* @param criteria criteria object.
* @return a new {@link Criteria} object.
* @since 1.1
*/
public Criteria or(Criteria criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
return or(Collections.singletonList(criteria));
}
/**
* Create a new {@link Criteria} and combine it as group with {@code OR} using the provided {@link Criteria} group.
*
* @param criteria criteria object.
* @return a new {@link Criteria} object.
* @since 1.1
*/
public Criteria or(List<Criteria> criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
return new Criteria(Criteria.this, Combinator.OR, criteria);
}
/**
* Creates a new {@link Criteria} with the given "ignore case" flag.
*
* @param ignoreCase {@literal true} if comparison should be done in case-insensitive way
* @return a new {@link Criteria} object
*/
public Criteria ignoreCase(boolean ignoreCase) {
if (this.ignoreCase != ignoreCase) {
return new Criteria(previous, combinator, group, column, comparator, value, ignoreCase);
}
return this;
}
/**
* @return the previous {@link Criteria} object. Can be {@literal null} if there is no previous {@link Criteria}.
* @see #hasPrevious()
*/
@Nullable
public Criteria getPrevious() {
return previous;
}
/**
* @return {@literal true} if this {@link Criteria} has a previous one.
*/
public boolean hasPrevious() {
return previous != null;
}
/**
* @return {@literal true} if this {@link Criteria} is empty.
* @since 1.1
*/
@Override
public boolean isEmpty() {
if (!doIsEmpty()) {
return false;
}
Criteria parent = this.previous;
while (parent != null) {
if (!parent.doIsEmpty()) {
return false;
}
parent = parent.previous;
}
return true;
}
private boolean doIsEmpty() {
if (this.comparator == Comparator.INITIAL) {
return true;
}
if (this.column != null) {
return false;
}
for (Criteria criteria : group) {
if (!criteria.isEmpty()) {
return false;
}
}
return true;
}
/**
* @return {@literal true} if this {@link Criteria} is empty.
*/
public boolean isGroup() {
return !this.group.isEmpty();
}
/**
* @return {@link Combinator} to combine this criteria with a previous one.
*/
public Combinator getCombinator() {
return combinator;
}
public List<Criteria> getGroup() {
return group;
}
/**
* @return the column/property name.
*/
@Nullable
public SqlIdentifier getColumn() {
return column;
}
/**
* @return {@link Comparator}.
*/
@Nullable
public Comparator getComparator() {
return comparator;
}
/**
* @return the comparison value. Can be {@literal null}.
*/
@Nullable
public Object getValue() {
return value;
}
/**
* Checks whether comparison should be done in case-insensitive way.
*
* @return {@literal true} if comparison should be done in case-insensitive way
*/
@Override
public boolean isIgnoreCase() {
return ignoreCase;
}
/**
* Interface declaring terminal builder methods to build a {@link Criteria}.
*/
public interface CriteriaStep {
/**
* Creates a {@link Criteria} using equality.
*
* @param value must not be {@literal null}.
*/
Criteria is(Object value);
/**
* Creates a {@link Criteria} using equality (is not).
*
* @param value must not be {@literal null}.
*/
Criteria not(Object value);
/**
* Creates a {@link Criteria} using {@code IN}.
*
* @param values must not be {@literal null}.
*/
Criteria in(Object... values);
/**
* Creates a {@link Criteria} using {@code IN}.
*
* @param values must not be {@literal null}.
*/
Criteria in(Collection<?> values);
/**
* Creates a {@link Criteria} using {@code NOT IN}.
*
* @param values must not be {@literal null}.
*/
Criteria notIn(Object... values);
/**
* Creates a {@link Criteria} using {@code NOT IN}.
*
* @param values must not be {@literal null}.
*/
Criteria notIn(Collection<?> values);
/**
* Creates a {@link Criteria} using less-than ({@literal <}).
*
* @param value must not be {@literal null}.
*/
Criteria lessThan(Object value);
/**
* Creates a {@link Criteria} using less-than or equal to ({@literal <=}).
*
* @param value must not be {@literal null}.
*/
Criteria lessThanOrEquals(Object value);
/**
* Creates a {@link Criteria} using greater-than({@literal >}).
*
* @param value must not be {@literal null}.
*/
Criteria greaterThan(Object value);
/**
* Creates a {@link Criteria} using greater-than or equal to ({@literal >=}).
*
* @param value must not be {@literal null}.
*/
Criteria greaterThanOrEquals(Object value);
/**
* Creates a {@link Criteria} using {@code LIKE}.
*
* @param value must not be {@literal null}.
*/
Criteria like(Object value);
/**
* Creates a {@link Criteria} using {@code NOT LIKE}.
*
* @param value must not be {@literal null}
* @return a new {@link Criteria} object
*/
Criteria notLike(Object value);
/**
* Creates a {@link Criteria} using {@code IS NULL}.
*/
Criteria isNull();
/**
* Creates a {@link Criteria} using {@code IS NOT NULL}.
*/
Criteria isNotNull();
/**
* Creates a {@link Criteria} using {@code IS TRUE}.
*
* @return a new {@link Criteria} object
*/
Criteria isTrue();
/**
* Creates a {@link Criteria} using {@code IS FALSE}.
*
* @return a new {@link Criteria} object
*/
Criteria isFalse();
}
/**
* Default {@link CriteriaStep} implementation.
*/
static class DefaultCriteriaStep implements CriteriaStep {
private final SqlIdentifier property;
DefaultCriteriaStep(SqlIdentifier property) {
this.property = property;
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#is(java.lang.Object)
*/
@Override
public Criteria is(Object value) {
Assert.notNull(value, "Value must not be null!");
return createCriteria(Comparator.EQ, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#not(java.lang.Object)
*/
@Override
public Criteria not(Object value) {
Assert.notNull(value, "Value must not be null!");
return createCriteria(Comparator.NEQ, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#in(java.lang.Object[])
*/
@Override
public Criteria in(Object... values) {
Assert.notNull(values, "Values must not be null!");
Assert.noNullElements(values, "Values must not contain a null value!");
if (values.length > 1 && values[1] instanceof Collection) {
throw new InvalidDataAccessApiUsageException(
"You can only pass in one argument of type " + values[1].getClass().getName());
}
return createCriteria(Comparator.IN, Arrays.asList(values));
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#in(java.util.Collection)
*/
@Override
public Criteria in(Collection<?> values) {
Assert.notNull(values, "Values must not be null!");
Assert.noNullElements(values.toArray(), "Values must not contain a null value!");
return createCriteria(Comparator.IN, values);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#notIn(java.lang.Object[])
*/
@Override
public Criteria notIn(Object... values) {
Assert.notNull(values, "Values must not be null!");
Assert.noNullElements(values, "Values must not contain a null value!");
if (values.length > 1 && values[1] instanceof Collection) {
throw new InvalidDataAccessApiUsageException(
"You can only pass in one argument of type " + values[1].getClass().getName());
}
return createCriteria(Comparator.NOT_IN, Arrays.asList(values));
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#notIn(java.util.Collection)
*/
@Override
public Criteria notIn(Collection<?> values) {
Assert.notNull(values, "Values must not be null!");
Assert.noNullElements(values.toArray(), "Values must not contain a null value!");
return createCriteria(Comparator.NOT_IN, values);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#lessThan(java.lang.Object)
*/
@Override
public Criteria lessThan(Object value) {
Assert.notNull(value, "Value must not be null!");
return createCriteria(Comparator.LT, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#lessThanOrEquals(java.lang.Object)
*/
@Override
public Criteria lessThanOrEquals(Object value) {
Assert.notNull(value, "Value must not be null!");
return createCriteria(Comparator.LTE, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#greaterThan(java.lang.Object)
*/
@Override
public Criteria greaterThan(Object value) {
Assert.notNull(value, "Value must not be null!");
return createCriteria(Comparator.GT, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#greaterThanOrEquals(java.lang.Object)
*/
@Override
public Criteria greaterThanOrEquals(Object value) {
Assert.notNull(value, "Value must not be null!");
return createCriteria(Comparator.GTE, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#like(java.lang.Object)
*/
@Override
public Criteria like(Object value) {
Assert.notNull(value, "Value must not be null!");
return createCriteria(Comparator.LIKE, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#notLike(java.lang.Object)
*/
@Override
public Criteria notLike(Object value) {
Assert.notNull(value, "Value must not be null!");
return createCriteria(Comparator.NOT_LIKE, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#isNull()
*/
@Override
public Criteria isNull() {
return createCriteria(Comparator.IS_NULL, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#isNotNull()
*/
@Override
public Criteria isNotNull() {
return createCriteria(Comparator.IS_NOT_NULL, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#isTrue()
*/
@Override
public Criteria isTrue() {
return createCriteria(Comparator.IS_TRUE, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#isFalse()
*/
@Override
public Criteria isFalse() {
return createCriteria(Comparator.IS_FALSE, null);
}
protected Criteria createCriteria(Comparator comparator, Object value) {
return new Criteria(this.property, comparator, value);
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2020 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.relational.core.query;
import java.util.Arrays;
import java.util.List;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Interface defining a criteria definition object. A criteria definition may chain multiple predicates and may also
* represent a group of nested criteria objects.
*
* @author Mark Paluch
* @since 2.0
*/
public interface CriteriaDefinition {
/**
* Static factory method to create an empty Criteria.
*
* @return an empty {@link Criteria}.
*/
static CriteriaDefinition empty() {
return Criteria.EMPTY;
}
/**
* Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link List Criterias}.
*
* @return new {@link Criteria}.
*/
static CriteriaDefinition from(Criteria... criteria) {
Assert.notNull(criteria, "Criteria must not be null");
Assert.noNullElements(criteria, "Criteria must not contain null elements");
return from(Arrays.asList(criteria));
}
/**
* Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link List Criterias}.
*
* @return new {@link Criteria}.
* @since 1.1
*/
static CriteriaDefinition from(List<Criteria> criteria) {
Assert.notNull(criteria, "Criteria must not be null");
Assert.noNullElements(criteria, "Criteria must not contain null elements");
if (criteria.isEmpty()) {
return Criteria.EMPTY;
}
if (criteria.size() == 1) {
return criteria.get(0);
}
return Criteria.EMPTY.and(criteria);
}
/**
* @return {@literal true} if this {@link Criteria} is empty.
*/
boolean isGroup();
List<? extends CriteriaDefinition> getGroup();
/**
* @return the column/property name.
*/
@Nullable
SqlIdentifier getColumn();
/**
* @return {@link Criteria.Comparator}.
*/
@Nullable
Comparator getComparator();
/**
* @return the comparison value. Can be {@literal null}.
*/
@Nullable
Object getValue();
/**
* Checks whether comparison should be done in case-insensitive way.
*
* @return {@literal true} if comparison should be done in case-insensitive way
*/
boolean isIgnoreCase();
/**
* @return the previous {@link CriteriaDefinition} object. Can be {@literal null} if there is no previous
* {@link CriteriaDefinition}.
* @see #hasPrevious()
*/
@Nullable
CriteriaDefinition getPrevious();
/**
* @return {@literal true} if this {@link Criteria} has a previous one.
*/
boolean hasPrevious();
/**
* @return {@literal true} if this {@link Criteria} is empty.
*/
boolean isEmpty();
/**
* @return {@link Combinator} to combine this criteria with a previous one.
*/
Combinator getCombinator();
enum Combinator {
INITIAL, AND, OR;
}
enum Comparator {
INITIAL, EQ, NEQ, LT, LTE, GT, GTE, IS_NULL, IS_NOT_NULL, LIKE, NOT_LIKE, NOT_IN, IN, IS_TRUE, IS_FALSE
}
}

View File

@@ -0,0 +1,260 @@
/*
* Copyright 2020 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.relational.core.query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Query object representing {@link Criteria}, columns, {@link Sort}, and limit/offset for a SQL query. {@link Query} is
* created with a fluent API creating immutable objects.
*
* @author Mark Paluch
* @since 2.0
* @see Criteria
* @see Sort
* @see Pageable
*/
public class Query {
private final @Nullable CriteriaDefinition criteria;
private final List<SqlIdentifier> columns;
private final Sort sort;
private final int limit;
private final long offset;
/**
* Static factory method to create a {@link Query} using the provided {@link CriteriaDefinition}.
*
* @param criteria must not be {@literal null}.
* @return a new {@link Query} for the given {@link Criteria}.
*/
public static Query query(CriteriaDefinition criteria) {
return new Query(criteria);
}
/**
* Creates a new {@link Query} using the given {@link Criteria}.
*
* @param criteria must not be {@literal null}.
*/
private Query(@Nullable CriteriaDefinition criteria) {
this(criteria, Collections.emptyList(), Sort.unsorted(), -1, -1);
}
private Query(@Nullable CriteriaDefinition criteria, List<SqlIdentifier> columns, Sort sort, int limit, long offset) {
this.criteria = criteria;
this.columns = columns;
this.sort = sort;
this.limit = limit;
this.offset = offset;
}
/**
* Create a new empty {@link Query}.
*
* @return
*/
public static Query empty() {
return new Query(null);
}
/**
* Add columns to the query.
*
* @param columns
* @return a new {@link Query} object containing the former settings with {@code columns} applied.
*/
public Query columns(String... columns) {
Assert.notNull(columns, "Columns must not be null");
return withColumns(Arrays.stream(columns).map(SqlIdentifier::unquoted).collect(Collectors.toList()));
}
/**
* Add columns to the query.
*
* @param columns
* @return a new {@link Query} object containing the former settings with {@code columns} applied.
*/
public Query columns(Collection<String> columns) {
Assert.notNull(columns, "Columns must not be null");
return withColumns(columns.stream().map(SqlIdentifier::unquoted).collect(Collectors.toList()));
}
/**
* Add columns to the query.
*
* @param columns
* @return a new {@link Query} object containing the former settings with {@code columns} applied.
* @since 1.1
*/
public Query columns(SqlIdentifier... columns) {
Assert.notNull(columns, "Columns must not be null");
return withColumns(Arrays.asList(columns));
}
/**
* Add columns to the query.
*
* @param columns
* @return a new {@link Query} object containing the former settings with {@code columns} applied.
*/
private Query withColumns(Collection<SqlIdentifier> columns) {
Assert.notNull(columns, "Columns must not be null");
List<SqlIdentifier> newColumns = new ArrayList<>(this.columns);
newColumns.addAll(columns);
return new Query(this.criteria, newColumns, this.sort, this.limit, offset);
}
/**
* Set number of rows to skip before returning results.
*
* @param offset
* @return a new {@link Query} object containing the former settings with {@code offset} applied.
*/
public Query offset(long offset) {
return new Query(this.criteria, this.columns, this.sort, this.limit, offset);
}
/**
* Limit the number of returned documents to {@code limit}.
*
* @param limit
* @return a new {@link Query} object containing the former settings with {@code limit} applied.
*/
public Query limit(int limit) {
return new Query(this.criteria, this.columns, this.sort, limit, this.offset);
}
/**
* Set the given pagination information on the {@link Query} instance. Will transparently set {@code offset} and
* {@code limit} as well as applying the {@link Sort} instance defined with the {@link Pageable}.
*
* @param pageable
* @return a new {@link Query} object containing the former settings with {@link Pageable} applied.
*/
public Query with(Pageable pageable) {
if (pageable.isUnpaged()) {
return this;
}
assertNoCaseSort(pageable.getSort());
return new Query(this.criteria, this.columns, this.sort.and(sort), pageable.getPageSize(), pageable.getOffset());
}
/**
* Add a {@link Sort} to the {@link Query} instance.
*
* @param sort
* @return a new {@link Query} object containing the former settings with {@link Sort} applied.
*/
public Query sort(Sort sort) {
Assert.notNull(sort, "Sort must not be null!");
if (sort.isUnsorted()) {
return this;
}
assertNoCaseSort(sort);
return new Query(this.criteria, this.columns, this.sort.and(sort), this.limit, this.offset);
}
/**
* Return the {@link Criteria} to be applied.
*
* @return
*/
public Optional<CriteriaDefinition> getCriteria() {
return Optional.ofNullable(this.criteria);
}
/**
* Return the columns that this query should project.
*
* @return
*/
public List<SqlIdentifier> getColumns() {
return columns;
}
/**
* Return {@literal true} if the {@link Query} has a sort parameter.
*
* @return {@literal true} if sorted.
* @see Sort#isSorted()
*/
public boolean isSorted() {
return sort.isSorted();
}
public Sort getSort() {
return sort;
}
/**
* Return the number of rows to skip.
*
* @return
*/
public long getOffset() {
return this.offset;
}
/**
* Return the maximum number of rows to be return.
*
* @return
*/
public int getLimit() {
return this.limit;
}
private static void assertNoCaseSort(Sort sort) {
for (Sort.Order order : sort) {
if (order.isIgnoreCase()) {
throw new IllegalArgumentException(String.format(
"Given sort contained an Order for %s with ignore case;" + "Ignore case sorting is not supported",
order.getProperty()));
}
}
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2019-2020 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.relational.core.query;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Class to easily construct SQL update assignments.
*
* @author Mark Paluch
* @author Oliver Drotbohm
* @since 2.0
*/
public class Update {
private static final Update EMPTY = new Update(Collections.emptyMap());
private final Map<SqlIdentifier, Object> columnsToUpdate;
private Update(Map<SqlIdentifier, Object> columnsToUpdate) {
this.columnsToUpdate = columnsToUpdate;
}
/**
* Static factory method to create an {@link Update} from {@code assignments}.
*
* @param assignments must not be {@literal null}.
* @return
*/
public static Update from(Map<SqlIdentifier, Object> assignments) {
return new Update(new LinkedHashMap<>(assignments));
}
/**
* Static factory method to create an {@link Update} using the provided column.
*
* @param column must not be {@literal null}.
* @param value can be {@literal null}.
* @return
*/
public static Update update(String column, @Nullable Object value) {
return EMPTY.set(column, value);
}
/**
* Update a column by assigning a value.
*
* @param column must not be {@literal null}.
* @param value can be {@literal null}.
* @return
*/
public Update set(String column, @Nullable Object value) {
Assert.hasText(column, "Column for update must not be null or blank");
return addMultiFieldOperation(SqlIdentifier.unquoted(column), value);
}
/**
* Update a column by assigning a value.
*
* @param column must not be {@literal null}.
* @param value can be {@literal null}.
* @return
* @since 1.1
*/
public Update set(SqlIdentifier column, @Nullable Object value) {
return addMultiFieldOperation(column, value);
}
/**
* Returns all assignments.
*
* @return
*/
public Map<SqlIdentifier, Object> getAssignments() {
return Collections.unmodifiableMap(this.columnsToUpdate);
}
private Update addMultiFieldOperation(SqlIdentifier key, @Nullable Object value) {
Assert.notNull(key, "Column for update must not be null");
Map<SqlIdentifier, Object> updates = new LinkedHashMap<>(this.columnsToUpdate);
updates.put(key, value);
return new Update(updates);
}
}

View File

@@ -0,0 +1,6 @@
/**
* Query and update support.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.relational.core.query;

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020 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.relational.core.query;
import static org.junit.Assert.*;
/**
* @author Mark Paluch
*/
public class CriteriaTests {
}

View File

@@ -0,0 +1,293 @@
/*
* Copyright 2019-2020 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.relational.core.query;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.SoftAssertions.*;
import static org.springframework.data.relational.core.query.Criteria.*;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.data.relational.core.sql.SqlIdentifier;
/**
* Unit tests for {@link Criteria}.
*
* @author Mark Paluch
* @author Jens Schauder
* @author Roman Chigvintsev
*/
public class CriteriaUnitTests {
@Test // DATAJDBC-513
public void fromCriteria() {
Criteria nested1 = where("foo").isNotNull();
Criteria nested2 = where("foo").isNull();
Criteria criteria = Criteria.from(nested1, nested2);
assertThat(criteria.isGroup()).isTrue();
assertThat(criteria.getGroup()).containsExactly(nested1, nested2);
assertThat(criteria.getPrevious()).isEqualTo(Criteria.empty());
}
@Test // DATAJDBC-513
public void fromCriteriaOptimized() {
Criteria nested = where("foo").is("bar").and("baz").isNotNull();
Criteria criteria = Criteria.from(nested);
assertThat(criteria).isSameAs(nested);
}
@Test // DATAJDBC-513
public void isEmpty() {
assertSoftly(softly -> {
Criteria empty = empty();
Criteria notEmpty = where("foo").is("bar");
assertThat(empty.isEmpty()).isTrue();
assertThat(notEmpty.isEmpty()).isFalse();
assertThat(Criteria.from(notEmpty).isEmpty()).isFalse();
assertThat(Criteria.from(notEmpty, notEmpty).isEmpty()).isFalse();
assertThat(Criteria.from(empty).isEmpty()).isTrue();
assertThat(Criteria.from(empty, empty).isEmpty()).isTrue();
assertThat(Criteria.from(empty, notEmpty).isEmpty()).isFalse();
assertThat(Criteria.from(notEmpty, empty).isEmpty()).isFalse();
});
}
@Test // DATAJDBC-513
public void andChainedCriteria() {
Criteria criteria = where("foo").is("bar").and("baz").isNotNull();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("baz"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.IS_NOT_NULL);
assertThat(criteria.getValue()).isNull();
assertThat(criteria.getPrevious()).isNotNull();
assertThat(criteria.getCombinator()).isEqualTo(Criteria.Combinator.AND);
criteria = criteria.getPrevious();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.EQ);
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test // DATAJDBC-513
public void andGroupedCriteria() {
Criteria criteria = where("foo").is("bar").and(where("foo").is("baz"));
assertThat(criteria.isGroup()).isTrue();
assertThat(criteria.getGroup()).hasSize(1);
assertThat(criteria.getGroup().get(0).getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getCombinator()).isEqualTo(Criteria.Combinator.AND);
criteria = criteria.getPrevious();
assertThat(criteria).isNotNull();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.EQ);
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test // DATAJDBC-513
public void orChainedCriteria() {
Criteria criteria = where("foo").is("bar").or("baz").isNotNull();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("baz"));
assertThat(criteria.getCombinator()).isEqualTo(Criteria.Combinator.OR);
criteria = criteria.getPrevious();
assertThat(criteria).isNotNull();
assertThat(criteria.getPrevious()).isNull();
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test // DATAJDBC-513
public void orGroupedCriteria() {
Criteria criteria = where("foo").is("bar").or(where("foo").is("baz"));
assertThat(criteria.isGroup()).isTrue();
assertThat(criteria.getGroup()).hasSize(1);
assertThat(criteria.getGroup().get(0).getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getCombinator()).isEqualTo(Criteria.Combinator.OR);
criteria = criteria.getPrevious();
assertThat(criteria).isNotNull();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.EQ);
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test // DATAJDBC-513
public void shouldBuildEqualsCriteria() {
Criteria criteria = where("foo").is("bar");
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.EQ);
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test
public void shouldBuildEqualsIgnoreCaseCriteria() {
Criteria criteria = where("foo").is("bar").ignoreCase(true);
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.EQ);
assertThat(criteria.getValue()).isEqualTo("bar");
assertThat(criteria.isIgnoreCase()).isTrue();
}
@Test // DATAJDBC-513
public void shouldBuildNotEqualsCriteria() {
Criteria criteria = where("foo").not("bar");
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.NEQ);
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test // DATAJDBC-513
public void shouldBuildInCriteria() {
Criteria criteria = where("foo").in("bar", "baz");
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.IN);
assertThat(criteria.getValue()).isEqualTo(Arrays.asList("bar", "baz"));
}
@Test // DATAJDBC-513
public void shouldBuildNotInCriteria() {
Criteria criteria = where("foo").notIn("bar", "baz");
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.NOT_IN);
assertThat(criteria.getValue()).isEqualTo(Arrays.asList("bar", "baz"));
}
@Test // DATAJDBC-513
public void shouldBuildGtCriteria() {
Criteria criteria = where("foo").greaterThan(1);
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.GT);
assertThat(criteria.getValue()).isEqualTo(1);
}
@Test // DATAJDBC-513
public void shouldBuildGteCriteria() {
Criteria criteria = where("foo").greaterThanOrEquals(1);
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.GTE);
assertThat(criteria.getValue()).isEqualTo(1);
}
@Test // DATAJDBC-513
public void shouldBuildLtCriteria() {
Criteria criteria = where("foo").lessThan(1);
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.LT);
assertThat(criteria.getValue()).isEqualTo(1);
}
@Test // DATAJDBC-513
public void shouldBuildLteCriteria() {
Criteria criteria = where("foo").lessThanOrEquals(1);
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.LTE);
assertThat(criteria.getValue()).isEqualTo(1);
}
@Test // DATAJDBC-513
public void shouldBuildLikeCriteria() {
Criteria criteria = where("foo").like("hello%");
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.LIKE);
assertThat(criteria.getValue()).isEqualTo("hello%");
}
@Test
public void shouldBuildNotLikeCriteria() {
Criteria criteria = where("foo").notLike("hello%");
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.NOT_LIKE);
assertThat(criteria.getValue()).isEqualTo("hello%");
}
@Test // DATAJDBC-513
public void shouldBuildIsNullCriteria() {
Criteria criteria = where("foo").isNull();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.IS_NULL);
}
@Test // DATAJDBC-513
public void shouldBuildIsNotNullCriteria() {
Criteria criteria = where("foo").isNotNull();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.IS_NOT_NULL);
}
@Test // DATAJDBC-513
public void shouldBuildIsTrueCriteria() {
Criteria criteria = where("foo").isTrue();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.IS_TRUE);
}
@Test // DATAJDBC-513
public void shouldBuildIsFalseCriteria() {
Criteria criteria = where("foo").isFalse();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.IS_FALSE);
}
}