Skip to content

Commit

Permalink
Ignore collection like attributes for query by example.
Browse files Browse the repository at this point in the history
Collection valued attributes now get ignored.
Before RelationalExampleMapper tried to generate predicates for these, resulting in invalid SQL.

Closes #1969
  • Loading branch information
schauder authored and mp911de committed Jan 13, 2025
1 parent 1c9c014 commit f2224a9
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
*
* @since 2.2
* @author Greg Turnquist
* @author Jens Schauder
*/
public class RelationalExampleMapper {

Expand Down Expand Up @@ -78,6 +79,10 @@ private <T> Query getMappedExample(Example<T> example, RelationalPersistentEntit

entity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {

if (property.isCollectionLike() || property.isMap()) {
return;
}

if (matcherAccessor.isIgnoredPath(property.getName())) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@

package org.springframework.data.relational.repository.query;

import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.domain.ExampleMatcher.*;
import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.*;
import static org.springframework.data.domain.ExampleMatcher.StringMatcher.*;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.query.Query;

import java.util.Objects;

import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.*;
import static org.springframework.data.domain.ExampleMatcher.StringMatcher.*;
import static org.springframework.data.domain.ExampleMatcher.*;
import org.springframework.lang.Nullable;

/**
* Verify that the {@link RelationalExampleMapper} properly turns {@link Example}s into {@link Query}'s.
Expand All @@ -48,8 +51,7 @@ public void before() {
@Test // GH-929
void queryByExampleWithId() {

Person person = new Person();
person.setId("id1");
Person person = new Person("id1", null, null, null, null, null);

Example<Person> example = Example.of(person);

Expand All @@ -63,8 +65,7 @@ void queryByExampleWithId() {
@Test // GH-929
void queryByExampleWithFirstname() {

Person person = new Person();
person.setFirstname("Frodo");
Person person = new Person(null, "Frodo", null, null, null, null);

Example<Person> example = Example.of(person);

Expand All @@ -78,9 +79,7 @@ void queryByExampleWithFirstname() {
@Test // GH-929
void queryByExampleWithFirstnameAndLastname() {

Person person = new Person();
person.setFirstname("Frodo");
person.setLastname("Baggins");
Person person = new Person(null, "Frodo", "Baggins", null, null, null);

Example<Person> example = Example.of(person);

Expand All @@ -94,8 +93,7 @@ void queryByExampleWithFirstnameAndLastname() {
@Test // GH-929
void queryByExampleWithNullMatchingLastName() {

Person person = new Person();
person.setLastname("Baggins");
Person person = new Person(null, null, "Baggins", null, null, null);

ExampleMatcher matcher = matching().withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
Expand All @@ -110,9 +108,7 @@ void queryByExampleWithNullMatchingLastName() {
@Test // GH-929
void queryByExampleWithNullMatchingFirstnameAndLastname() {

Person person = new Person();
person.setFirstname("Bilbo");
person.setLastname("Baggins");
Person person = new Person(null, "Bilbo", "Baggins", null, null, null);

ExampleMatcher matcher = matching().withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
Expand All @@ -127,9 +123,7 @@ void queryByExampleWithNullMatchingFirstnameAndLastname() {
@Test // GH-929
void queryByExampleWithFirstnameAndLastnameIgnoringFirstname() {

Person person = new Person();
person.setFirstname("Frodo");
person.setLastname("Baggins");
Person person = new Person(null, "Bilbo", "Baggins", null, null, null);

ExampleMatcher matcher = matching().withIgnorePaths("firstname");
Example<Person> example = Example.of(person, matcher);
Expand All @@ -144,9 +138,7 @@ void queryByExampleWithFirstnameAndLastnameIgnoringFirstname() {
@Test // GH-929
void queryByExampleWithFirstnameAndLastnameWithNullMatchingIgnoringFirstName() {

Person person = new Person();
person.setFirstname("Frodo");
person.setLastname("Baggins");
Person person = new Person(null, "Bilbo", "Baggins", null, null, null);

ExampleMatcher matcher = matching().withIncludeNullValues().withIgnorePaths("firstname");
Example<Person> example = Example.of(person, matcher);
Expand All @@ -161,8 +153,7 @@ void queryByExampleWithFirstnameAndLastnameWithNullMatchingIgnoringFirstName() {
@Test // GH-929
void queryByExampleWithFirstnameWithStringMatchingAtTheBeginning() {

Person person = new Person();
person.setFirstname("Fro");
Person person = new Person(null, "Fro", null, null, null, null);

ExampleMatcher matcher = matching().withStringMatcher(STARTING);
Example<Person> example = Example.of(person, matcher);
Expand All @@ -177,8 +168,7 @@ void queryByExampleWithFirstnameWithStringMatchingAtTheBeginning() {
@Test // GH-929
void queryByExampleWithFirstnameWithStringMatchingOnTheEnding() {

Person person = new Person();
person.setFirstname("do");
Person person = new Person(null, "do", null, null, null, null);

ExampleMatcher matcher = matching().withStringMatcher(ENDING);
Example<Person> example = Example.of(person, matcher);
Expand All @@ -193,8 +183,7 @@ void queryByExampleWithFirstnameWithStringMatchingOnTheEnding() {
@Test // GH-929
void queryByExampleWithFirstnameWithStringMatchingContaining() {

Person person = new Person();
person.setFirstname("do");
Person person = new Person(null, "do", null, null, null, null);

ExampleMatcher matcher = matching().withStringMatcher(CONTAINING);
Example<Person> example = Example.of(person, matcher);
Expand All @@ -209,8 +198,7 @@ void queryByExampleWithFirstnameWithStringMatchingContaining() {
@Test // GH-929
void queryByExampleWithFirstnameWithStringMatchingRegEx() {

Person person = new Person();
person.setFirstname("do");
Person person = new Person(null, "do", null, null, null, null);

ExampleMatcher matcher = matching().withStringMatcher(ExampleMatcher.StringMatcher.REGEX);
Example<Person> example = Example.of(person, matcher);
Expand All @@ -222,8 +210,7 @@ void queryByExampleWithFirstnameWithStringMatchingRegEx() {
@Test // GH-929
void queryByExampleWithFirstnameWithFieldSpecificStringMatcherEndsWith() {

Person person = new Person();
person.setFirstname("do");
Person person = new Person(null, "do", null, null, null, null);

ExampleMatcher matcher = matching().withMatcher("firstname", endsWith());
Example<Person> example = Example.of(person, matcher);
Expand All @@ -238,8 +225,7 @@ void queryByExampleWithFirstnameWithFieldSpecificStringMatcherEndsWith() {
@Test // GH-929
void queryByExampleWithFirstnameWithFieldSpecificStringMatcherStartsWith() {

Person person = new Person();
person.setFirstname("Fro");
Person person = new Person(null, "Fro", null, null, null, null);

ExampleMatcher matcher = matching().withMatcher("firstname", startsWith());
Example<Person> example = Example.of(person, matcher);
Expand All @@ -254,8 +240,7 @@ void queryByExampleWithFirstnameWithFieldSpecificStringMatcherStartsWith() {
@Test // GH-929
void queryByExampleWithFirstnameWithFieldSpecificStringMatcherContains() {

Person person = new Person();
person.setFirstname("do");
Person person = new Person(null, "do", null, null, null, null);

ExampleMatcher matcher = matching().withMatcher("firstname", contains());
Example<Person> example = Example.of(person, matcher);
Expand All @@ -270,8 +255,7 @@ void queryByExampleWithFirstnameWithFieldSpecificStringMatcherContains() {
@Test // GH-929
void queryByExampleWithFirstnameWithStringMatchingAtTheBeginningIncludingNull() {

Person person = new Person();
person.setFirstname("Fro");
Person person = new Person(null, "Fro", null, null, null, null);

ExampleMatcher matcher = matching().withStringMatcher(STARTING).withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
Expand All @@ -286,8 +270,7 @@ void queryByExampleWithFirstnameWithStringMatchingAtTheBeginningIncludingNull()
@Test // GH-929
void queryByExampleWithFirstnameWithStringMatchingOnTheEndingIncludingNull() {

Person person = new Person();
person.setFirstname("do");
Person person = new Person(null, "do", null, null, null, null);

ExampleMatcher matcher = matching().withStringMatcher(ENDING).withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
Expand All @@ -302,8 +285,7 @@ void queryByExampleWithFirstnameWithStringMatchingOnTheEndingIncludingNull() {
@Test // GH-929
void queryByExampleWithFirstnameIgnoreCaseFieldLevel() {

Person person = new Person();
person.setFirstname("fro");
Person person = new Person(null, "fro", null, null, null, null);

ExampleMatcher matcher = matching().withMatcher("firstname", startsWith().ignoreCase());
Example<Person> example = Example.of(person, matcher);
Expand All @@ -320,8 +302,7 @@ void queryByExampleWithFirstnameIgnoreCaseFieldLevel() {
@Test // GH-929
void queryByExampleWithFirstnameWithStringMatchingContainingIncludingNull() {

Person person = new Person();
person.setFirstname("do");
Person person = new Person(null, "do", null, null, null, null);

ExampleMatcher matcher = matching().withStringMatcher(CONTAINING).withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
Expand All @@ -336,8 +317,7 @@ void queryByExampleWithFirstnameWithStringMatchingContainingIncludingNull() {
@Test // GH-929
void queryByExampleWithFirstnameIgnoreCase() {

Person person = new Person();
person.setFirstname("Frodo");
Person person = new Person(null, "Frodo", null, null, null, null);

ExampleMatcher matcher = matching().withIgnoreCase(true);
Example<Person> example = Example.of(person, matcher);
Expand All @@ -354,9 +334,7 @@ void queryByExampleWithFirstnameIgnoreCase() {
@Test // GH-929
void queryByExampleWithFirstnameOrLastname() {

Person person = new Person();
person.setFirstname("Frodo");
person.setLastname("Baggins");
Person person = new Person(null, "Frodo", "Baggins", null, null, null);

ExampleMatcher matcher = matchingAny();
Example<Person> example = Example.of(person, matcher);
Expand All @@ -371,9 +349,7 @@ void queryByExampleWithFirstnameOrLastname() {
@Test // GH-929
void queryByExampleEvenHandlesInvisibleFields() {

Person person = new Person();
person.setFirstname("Frodo");
person.setSecret("I have the ring!");
Person person = new Person(null, "Frodo", null, "I have the ring!", null, null);

Example<Person> example = Example.of(person);

Expand All @@ -388,10 +364,7 @@ void queryByExampleEvenHandlesInvisibleFields() {
@Test // GH-929
void queryByExampleSupportsPropertyTransforms() {

Person person = new Person();
person.setFirstname("Frodo");
person.setLastname("Baggins");
person.setSecret("I have the ring!");
Person person = new Person(null, "Frodo", "Baggins", "I have the ring!", null, null);

ExampleMatcher matcher = matching() //
.withTransformer("firstname", o -> {
Expand All @@ -418,55 +391,33 @@ void queryByExampleSupportsPropertyTransforms() {
"(secret = 'I have the ring!')");
}

static class Person {
@Test // GH-1969
void collectionLikeAttributesGetIgnored() {

@Id
String id;
String firstname;
String lastname;
String secret;
Example<Person> example = Example.of(new Person(null, "Frodo", null, null, List.of(new Possession("Ring")), null));

public Person(String id, String firstname, String lastname, String secret) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.secret = secret;
}

public Person() {
}
Query query = exampleMapper.getMappedExample(example);

// Override default visibility of getting the secret.
private String getSecret() {
return this.secret;
}
assertThat(query.getCriteria().orElseThrow().toString()).doesNotContainIgnoringCase("possession");
}

public String getId() {
return this.id;
}
@Test // GH-1969
void mapAttributesGetIgnored() {

public String getFirstname() {
return this.firstname;
}
Example<Person> example = Example.of(new Person(null, "Frodo", null, null, null, Map.of("Home", new Address("Bag End"))));

public String getLastname() {
return this.lastname;
}
Query query = exampleMapper.getMappedExample(example);

public void setId(String id) {
this.id = id;
}
assertThat(query.getCriteria().orElseThrow().toString()).doesNotContainIgnoringCase("address");
}

public void setFirstname(String firstname) {
this.firstname = firstname;
}
record Person(@Id @Nullable String id, @Nullable String firstname, @Nullable String lastname, @Nullable String secret,
@Nullable List<Possession> possessions,@Nullable Map<String,Address> addresses) {
}

public void setLastname(String lastname) {
this.lastname = lastname;
}
record Possession(String name) {
}

public void setSecret(String secret) {
this.secret = secret;
}
record Address(String description) {
}
}
1 change: 1 addition & 0 deletions src/main/antora/modules/ROOT/pages/query-by-example.adoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
:support-qbe-collection: false
include::{commons}@data-commons::query-by-example.adoc[]

Here's an example:
Expand Down

0 comments on commit f2224a9

Please sign in to comment.