Skip to content

Fix implementation of equlas/hashcode for Criteria class. #3088

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

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.core.geo.GeoBox;
Expand Down Expand Up @@ -859,6 +860,7 @@ private List<Object> toCollection(Object... values) {

// endregion

// region equals/hashcode
@Override
public boolean equals(Object o) {
if (this == o)
Expand All @@ -874,6 +876,8 @@ public boolean equals(Object o) {
return false;
if (!Objects.equals(field, criteria.field))
return false;
if (!criteriaChain.filter(this).equals(criteria.criteriaChain.filter(criteria)))
return false;
if (!queryCriteriaEntries.equals(criteria.queryCriteriaEntries))
return false;
if (!filterCriteriaEntries.equals(criteria.filterCriteriaEntries))
Expand All @@ -886,11 +890,16 @@ public int hashCode() {
int result = field != null ? field.hashCode() : 0;
result = 31 * result + (boost != +0.0f ? Float.floatToIntBits(boost) : 0);
result = 31 * result + (negating ? 1 : 0);
// the criteriaChain contains "this" object, so we need to filter it out
// to avoid a stackoverflow here, because the hashcode implementation
// uses the element's hashcodes
result = 31 * result + criteriaChain.filter(this).hashCode();
result = 31 * result + queryCriteriaEntries.hashCode();
result = 31 * result + filterCriteriaEntries.hashCode();
result = 31 * result + subCriteria.hashCode();
return result;
}
// endregion

@Override
public String toString() {
Expand Down Expand Up @@ -938,7 +947,17 @@ public Operator getOperator() {
*
* @since 4.1
*/
public static class CriteriaChain extends LinkedList<Criteria> {}
public static class CriteriaChain extends LinkedList<Criteria> {
/**
* return a copy of this list with the given element filtered out.
*
* @param criteria the element to filter
* @return the filtered list
*/
List<Criteria> filter(Criteria criteria) {
return this.stream().filter(c -> c != criteria).collect(Collectors.toList());
}
}

/**
* Operator to join the entries of the criteria chain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
*/
package org.springframework.data.elasticsearch.client.elc;

import static org.assertj.core.api.Assertions.*;
import static org.skyscreamer.jsonassert.JSONAssert.*;
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.*;

import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -445,6 +447,108 @@ void shouldMapNamesInSourceStoredFields() {
softly.assertAll();
}

// the following test failed because of a wrong implementation in Criteria
// equals and hscode methods.
@Test // #3083
@DisplayName("should map correct subcriteria")
void shouldMapCorrectSubcriteria() throws JSONException {
Criteria criteria = new Criteria("first").is("hello");

List<Criteria> criterias = new ArrayList<>();
criterias.add(new Criteria().or("second").exists());

List<Criteria> subCriterias = new ArrayList<>();
subCriterias.add(new Criteria("third").exists()
.and(new Criteria("fourth").is("ciao")));
subCriterias.add(new Criteria("third").exists()
.and(new Criteria("fourth").is("hi")));

Criteria result = Criteria.or();

for (Criteria c : criterias) {
result = result.or(c);
}

for (Criteria c : subCriterias) {
result = result.subCriteria(c);
}
criteria = criteria.subCriteria(result);
CriteriaQuery criteriaQuery = new CriteriaQuery(criteria);

String expected = """
{
"bool": {
"must": [
{
"query_string": {
"default_operator": "and",
"fields": [
"first"
],
"query": "hello"
}
},
{
"bool": {
"should": [
{
"exists": {
"field": "second"
}
},
{
"bool": {
"must": [
{
"exists": {
"field": "third"
}
},
{
"query_string": {
"default_operator": "and",
"fields": [
"fourth"
],
"query": "ciao"
}
}
]
}
},
{
"bool": {
"must": [
{
"exists": {
"field": "third"
}
},
{
"query_string": {
"default_operator": "and",
"fields": [
"fourth"
],
"query": "hi"
}
}
]
}
}
]
}
}
]
}
}
""";

mappingElasticsearchConverter.updateQuery(criteriaQuery, Person.class);
var queryString = queryToJson(CriteriaQueryProcessor.createQuery(criteriaQuery.getCriteria()), mapper);

assertEquals(expected, queryString, false);
}
// endregion
// region helper functions

Expand Down