#64 - Add Criteria API.

We now support Criteria creation and mapping to express where conditions with a fluent API.

databaseClient.select().from("legoset")
  .where(Criteria.of("name").like("John%").and("id").lessThanOrEquals(42055));

databaseClient.delete()
  .from(LegoSet.class)
  .where(Criteria.of("id").is(42055))
  .then()

databaseClient.delete()
  .from(LegoSet.class)
  .where(Criteria.of("id").is(42055))
  .fetch()
  .rowsUpdated()

Original pull request: #106.
This commit is contained in:
Mark Paluch
2019-03-25 14:38:32 +01:00
committed by Oliver Drotbohm
parent 88945d7d71
commit fd4472aaaa
22 changed files with 2640 additions and 139 deletions

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2019 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.r2dbc.domain;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import io.r2dbc.spi.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.springframework.data.r2dbc.dialect.BindMarker;
import org.springframework.data.r2dbc.dialect.BindMarkers;
import org.springframework.data.r2dbc.dialect.BindMarkersFactory;
/**
* Unit tests for {@link Bindings}.
*
* @author Mark Paluch
*/
public class BindingsUnitTests {
BindMarkersFactory markersFactory = BindMarkersFactory.indexed("$", 1);
Statement statementMock = mock(Statement.class);
@Test // gh-64
public void shouldCreateBindings() {
MutableBindings bindings = new MutableBindings(markersFactory.create());
bindings.bind(bindings.nextMarker(), "foo");
bindings.bindNull(bindings.nextMarker(), String.class);
assertThat(bindings.stream()).hasSize(2);
}
@Test // gh-64
public void shouldApplyValueBinding() {
MutableBindings bindings = new MutableBindings(markersFactory.create());
bindings.bind(bindings.nextMarker(), "foo");
bindings.apply(statementMock);
verify(statementMock).bind(0, "foo");
}
@Test // gh-64
public void shouldApplySimpleValueBinding() {
MutableBindings bindings = new MutableBindings(markersFactory.create());
BindMarker marker = bindings.bind("foo");
bindings.apply(statementMock);
assertThat(marker.getPlaceholder()).isEqualTo("$1");
verify(statementMock).bind(0, "foo");
}
@Test // gh-64
public void shouldApplyNullBinding() {
MutableBindings bindings = new MutableBindings(markersFactory.create());
bindings.bindNull(bindings.nextMarker(), String.class);
bindings.apply(statementMock);
verify(statementMock).bindNull(0, String.class);
}
@Test // gh-64
public void shouldApplySimpleNullBinding() {
MutableBindings bindings = new MutableBindings(markersFactory.create());
BindMarker marker = bindings.bindNull(String.class);
bindings.apply(statementMock);
assertThat(marker.getPlaceholder()).isEqualTo("$1");
verify(statementMock).bindNull(0, String.class);
}
@Test // gh-64
public void shouldConsumeBindings() {
MutableBindings bindings = new MutableBindings(markersFactory.create());
bindings.bind(bindings.nextMarker(), "foo");
bindings.bindNull(bindings.nextMarker(), String.class);
AtomicInteger counter = new AtomicInteger();
bindings.forEach(binding -> {
if (binding.hasValue()) {
counter.incrementAndGet();
assertThat(binding.getValue()).isEqualTo("foo");
assertThat(binding.getBindMarker().getPlaceholder()).isEqualTo("$1");
}
if (binding.isNull()) {
counter.incrementAndGet();
assertThat(((Bindings.NullBinding) binding).getValueType()).isEqualTo(String.class);
assertThat(binding.getBindMarker().getPlaceholder()).isEqualTo("$2");
}
});
assertThat(counter).hasValue(2);
}
@Test // gh-64
public void shouldMergeBindings() {
BindMarkers markers = markersFactory.create();
BindMarker shared = markers.next();
BindMarker leftMarker = markers.next();
List<Bindings.Binding> left = new ArrayList<>();
left.add(new Bindings.NullBinding(shared, String.class));
left.add(new Bindings.ValueBinding(leftMarker, "left"));
BindMarker rightMarker = markers.next();
List<Bindings.Binding> right = new ArrayList<>();
left.add(new Bindings.ValueBinding(shared, "override"));
left.add(new Bindings.ValueBinding(rightMarker, "right"));
Bindings merged = Bindings.merge(new Bindings(left), new Bindings(right));
assertThat(merged).hasSize(3);
merged.apply(statementMock);
verify(statementMock).bind(0, "override");
verify(statementMock).bind(1, "left");
verify(statementMock).bind(2, "right");
}
}

View File

@@ -32,6 +32,7 @@ import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.function.query.Criteria;
import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -204,6 +205,43 @@ public abstract class AbstractDatabaseClientIntegrationTests extends R2dbcIntegr
assertThat(jdbc.queryForMap("SELECT id, name, manual FROM legoset")).containsEntry("id", 42055);
}
@Test // gh-64
public void deleteUntyped() {
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42055, 'SCHAUFELRADBAGGER', 12)");
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42064, 'FORSCHUNGSSCHIFF', 13)");
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
databaseClient.delete() //
.from("legoset") //
.where(Criteria.of("id").is(42055)) //
.fetch() //
.rowsUpdated() //
.as(StepVerifier::create) //
.expectNext(1).verifyComplete();
assertThat(jdbc.queryForList("SELECT id AS count FROM legoset")).hasSize(1);
}
@Test // gh-64
public void deleteTyped() {
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42055, 'SCHAUFELRADBAGGER', 12)");
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42064, 'FORSCHUNGSSCHIFF', 13)");
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
databaseClient.delete() //
.from(LegoSet.class) //
.where(Criteria.of("id").is(42055)) //
.then() //
.as(StepVerifier::create) //
.verifyComplete();
assertThat(jdbc.queryForList("SELECT id AS count FROM legoset")).hasSize(1);
}
@Test // gh-2
public void selectAsMap() {
@@ -241,6 +279,44 @@ public abstract class AbstractDatabaseClientIntegrationTests extends R2dbcIntegr
.verifyComplete();
}
@Test // gh-8
public void selectWithCriteria() {
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42055, 'SCHAUFELRADBAGGER', 12)");
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
databaseClient.select().from("legoset") //
.project("id", "name", "manual") //
.orderBy(Sort.by("id")) //
.where(Criteria.of("id").greaterThanOrEquals(42055).and("id").lessThanOrEquals(42055))
.map((r, md) -> r.get("id", Integer.class)) //
.all() //
.as(StepVerifier::create) //
.expectNext(42055) //
.verifyComplete();
}
@Test // gh-64
public void selectWithCriteriaIn() {
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42055, 'SCHAUFELRADBAGGER', 12)");
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42064, 'FORSCHUNGSSCHIFF', 13)");
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42068, 'FLUGHAFEN-LÖSCHFAHRZEUG', 13)");
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
databaseClient.select().from(LegoSet.class) //
.orderBy(Sort.by("id")) //
.where(Criteria.of("id").in(42055, 42064)) //
.map((r, md) -> r.get("id", Integer.class)) //
.all() //
.as(StepVerifier::create) //
.expectNext(42055) //
.expectNext(42064) //
.verifyComplete();
}
@Test // gh-2
public void selectOrderByIdDesc() {

View File

@@ -30,6 +30,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
/**

View File

@@ -27,6 +27,7 @@ import org.springframework.data.r2dbc.dialect.BindMarkersFactory;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.dialect.SqlServerDialect;
import org.springframework.data.r2dbc.domain.BindTarget;
import org.springframework.data.r2dbc.domain.BindableOperation;
/**
* Unit tests for {@link NamedParameterUtils}.

View File

@@ -0,0 +1,218 @@
/*
* Copyright 2019 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.r2dbc.function.query;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import io.r2dbc.spi.Statement;
import org.junit.Test;
import org.springframework.data.r2dbc.dialect.BindMarkersFactory;
import org.springframework.data.r2dbc.function.convert.MappingR2dbcConverter;
import org.springframework.data.r2dbc.function.convert.R2dbcConverter;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.sql.Table;
/**
* Unit tests for {@link CriteriaMapper}.
*
* @author Mark Paluch
*/
public class CriteriaMapperUnitTests {
R2dbcConverter converter = new MappingR2dbcConverter(new RelationalMappingContext());
CriteriaMapper mapper = new CriteriaMapper(converter);
Statement statementMock = mock(Statement.class);
@Test // gh-64
public void shouldMapSimpleCriteria() {
Criteria criteria = Criteria.of("name").is("foo");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name = ?[$1]");
bindings.getBindings().apply(statementMock);
verify(statementMock).bind(0, "foo");
}
@Test // gh-64
public void shouldConsiderColumnName() {
Criteria criteria = Criteria.of("alternative").is("foo");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.another_name = ?[$1]");
}
@Test // gh-64
public void shouldMapAndCriteria() {
Criteria criteria = Criteria.of("name").is("foo").and("bar").is("baz");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name = ?[$1] AND person.bar = ?[$2]");
bindings.getBindings().apply(statementMock);
verify(statementMock).bind(0, "foo");
verify(statementMock).bind(1, "baz");
}
@Test // gh-64
public void shouldMapOrCriteria() {
Criteria criteria = Criteria.of("name").is("foo").or("bar").is("baz");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name = ?[$1] OR person.bar = ?[$2]");
}
@Test // gh-64
public void shouldMapAndOrCriteria() {
Criteria criteria = Criteria.of("name").is("foo") //
.and("name").isNotNull() //
.or("bar").is("baz") //
.and("anotherOne").is("alternative");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo(
"person.name = ?[$1] AND person.name IS NOT NULL OR person.bar = ?[$2] AND person.anotherOne = ?[$3]");
}
@Test // gh-64
public void shouldMapNeq() {
Criteria criteria = Criteria.of("name").not("foo");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name != ?[$1]");
}
@Test // gh-64
public void shouldMapIsNull() {
Criteria criteria = Criteria.of("name").isNull();
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name IS NULL");
}
@Test // gh-64
public void shouldMapIsNotNull() {
Criteria criteria = Criteria.of("name").isNotNull();
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name IS NOT NULL");
}
@Test // gh-64
public void shouldMapIsIn() {
Criteria criteria = Criteria.of("name").in("a", "b", "c");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name IN (?[$1], ?[$2], ?[$3])");
}
@Test // gh-64
public void shouldMapIsNotIn() {
Criteria criteria = Criteria.of("name").notIn("a", "b", "c");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("NOT person.name IN (?[$1], ?[$2], ?[$3])");
}
@Test // gh-64
public void shouldMapIsGt() {
Criteria criteria = Criteria.of("name").greaterThan("a");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name > ?[$1]");
}
@Test // gh-64
public void shouldMapIsGte() {
Criteria criteria = Criteria.of("name").greaterThanOrEquals("a");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name >= ?[$1]");
}
@Test // gh-64
public void shouldMapIsLt() {
Criteria criteria = Criteria.of("name").lessThan("a");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name < ?[$1]");
}
@Test // gh-64
public void shouldMapIsLte() {
Criteria criteria = Criteria.of("name").lessThanOrEquals("a");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name <= ?[$1]");
}
@Test // gh-64
public void shouldMapIsLike() {
Criteria criteria = Criteria.of("name").like("a");
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition().toString()).isEqualTo("person.name LIKE ?[$1]");
}
@SuppressWarnings("unchecked")
private BoundCondition map(Criteria criteria) {
BindMarkersFactory markers = BindMarkersFactory.indexed("$", 1);
return mapper.getMappedObject(markers.create(), criteria, Table.create("person"),
converter.getMappingContext().getRequiredPersistentEntity(Person.class));
}
static class Person {
String name;
@Column("another_name") String alternative;
}
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2019 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.r2dbc.function.query;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.r2dbc.function.query.Criteria.*;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.data.r2dbc.function.query.Criteria.*;
/**
* Unit tests for {@link Criteria}.
*
* @author Mark Paluch
*/
public class CriteriaUnitTests {
@Test // gh-64
public void andChainedCriteria() {
Criteria criteria = of("foo").is("bar").and("baz").isNotNull();
assertThat(criteria.getProperty()).isEqualTo("baz");
assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_NOT_NULL);
assertThat(criteria.getValue()).isNull();
assertThat(criteria.getPrevious()).isNotNull();
assertThat(criteria.getCombinator()).isEqualTo(Combinator.AND);
criteria = criteria.getPrevious();
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.EQ);
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test // gh-64
public void orChainedCriteria() {
Criteria criteria = of("foo").is("bar").or("baz").isNotNull();
assertThat(criteria.getProperty()).isEqualTo("baz");
assertThat(criteria.getCombinator()).isEqualTo(Combinator.OR);
criteria = criteria.getPrevious();
assertThat(criteria.getPrevious()).isNull();
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test // gh-64
public void shouldBuildEqualsCriteria() {
Criteria criteria = of("foo").is("bar");
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.EQ);
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test // gh-64
public void shouldBuildNotEqualsCriteria() {
Criteria criteria = of("foo").not("bar");
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.NEQ);
assertThat(criteria.getValue()).isEqualTo("bar");
}
@Test // gh-64
public void shouldBuildInCriteria() {
Criteria criteria = of("foo").in("bar", "baz");
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.IN);
assertThat(criteria.getValue()).isEqualTo(Arrays.asList("bar", "baz"));
}
@Test // gh-64
public void shouldBuildNotInCriteria() {
Criteria criteria = of("foo").notIn("bar", "baz");
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.NOT_IN);
assertThat(criteria.getValue()).isEqualTo(Arrays.asList("bar", "baz"));
}
@Test // gh-64
public void shouldBuildGtCriteria() {
Criteria criteria = of("foo").greaterThan(1);
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.GT);
assertThat(criteria.getValue()).isEqualTo(1);
}
@Test // gh-64
public void shouldBuildGteCriteria() {
Criteria criteria = of("foo").greaterThanOrEquals(1);
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.GTE);
assertThat(criteria.getValue()).isEqualTo(1);
}
@Test // gh-64
public void shouldBuildLtCriteria() {
Criteria criteria = of("foo").lessThan(1);
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.LT);
assertThat(criteria.getValue()).isEqualTo(1);
}
@Test // gh-64
public void shouldBuildLteCriteria() {
Criteria criteria = of("foo").lessThanOrEquals(1);
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.LTE);
assertThat(criteria.getValue()).isEqualTo(1);
}
@Test // gh-64
public void shouldBuildLikeCriteria() {
Criteria criteria = of("foo").like("hello%");
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.LIKE);
assertThat(criteria.getValue()).isEqualTo("hello%");
}
@Test // gh-64
public void shouldBuildIsNullCriteria() {
Criteria criteria = of("foo").isNull();
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_NULL);
}
@Test // gh-64
public void shouldBuildIsNotNullCriteria() {
Criteria criteria = of("foo").isNotNull();
assertThat(criteria.getProperty()).isEqualTo("foo");
assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_NOT_NULL);
}
}