Polishing.

Turn newly introduced methods on ParameterAccessor into default ones allowing modules to pick up changes at their own pace.
Add issue references and missing documentation.
Align Search- and GeoResults toString method with Page.

Original Pull Request: #3285
This commit is contained in:
Christoph Strobl
2025-05-07 15:15:33 +02:00
committed by Mark Paluch
parent ecb2592493
commit 9a7ea62b2e
11 changed files with 56 additions and 42 deletions

View File

@@ -80,11 +80,11 @@ Score values are not part of a domain model and therefore represented best as ou
Generally, a Score is computed by a `ScoringFunction`.
The actual scoring function used to calculate this score can depends on the underlying database and can be obtained from a search index or input parameters.
Spring Data supports declares constants for commonly used functions such as:
Spring Data support declares constants for commonly used functions such as:
Euclidean distance:: Calculates the straight-line distance in n-dimensional space involving the square root of the sum of squared differences.
Cosine similarity:: Measures the angle between two vectors by calculating the Dot product first and then normalizing its result by dividing by the product of their lengths.
Dot product:: Computes the sum of element-wise multiplications.
Euclidean Distance:: Calculates the straight-line distance in n-dimensional space involving the square root of the sum of squared differences.
Cosine Similarity:: Measures the angle between two vectors by calculating the Dot product first and then normalizing its result by dividing by the product of their lengths.
Dot Product:: Computes the sum of element-wise multiplications.
The choice of similarity function can impact both the performance and semantics of the search and is often determined by the underlying database or index being used.
Spring Data adopts to the database's native scoring function capabilities and whether the score can be used to limit results.
@@ -107,7 +107,7 @@ Generally, you have the choice of declaring a search method using two approaches
* Query Derivation
* Declaring a String-based Query
Generally, Vector Search methods must declare a `Vector` parameter to define the query vector.
Vector Search methods must declare a `Vector` parameter to define the query vector.
[[vector-search.method.derivation]]
=== Derived Search Methods
@@ -142,7 +142,7 @@ endif::[]
With more control over the actual query, Spring Data can make fewer assumptions about the query and its parameters.
For example, `Similarity` normalization uses the native score function within the query to normalize the given similarity into a score predicate value and vice versa.
If an annotated query doesn't define e.g. the score, then the score value in the returned `SearchResult<T>` will be zero.
If an annotated query does not define e.g. the score, then the score value in the returned `SearchResult<T>` will be zero.
[[vector-search.method.sorting]]
=== Sorting

View File

@@ -20,7 +20,6 @@ import java.io.Serializable;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

View File

@@ -26,7 +26,6 @@ import java.util.stream.Stream;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Value object encapsulating a collection of {@link SearchResult} instances.
@@ -123,8 +122,7 @@ public class SearchResults<T> implements Iterable<SearchResult<T>>, Serializable
@Override
public String toString() {
return results.isEmpty() ? "SearchResults: [empty]"
: String.format("SearchResults: [results: %s]", StringUtils.collectionToCommaDelimitedString(results));
return results.isEmpty() ? "SearchResults [empty]" : String.format("SearchResults [size: %s]", results.size());
}
}

View File

@@ -19,7 +19,6 @@ import java.io.Serial;
import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -79,6 +78,6 @@ public final class GeoResult<T> implements Serializable {
@Override
public String toString() {
return String.format("GeoResult [content: %s, distance: %s, ]", content.toString(), distance.toString());
return String.format("GeoResult [content: %s, distance: %s]", content, distance);
}
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.geo;
import java.io.Serial;
import java.io.Serializable;
import java.util.Collections;
@@ -23,11 +22,9 @@ import java.util.Iterator;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Value object to capture {@link GeoResult}s as well as the average distance they have.
@@ -129,8 +126,8 @@ public class GeoResults<T> implements Iterable<GeoResult<T>>, Serializable {
@Override
public String toString() {
return String.format("GeoResults: [averageDistance: %s, results: %s]", averageDistance.toString(),
StringUtils.collectionToCommaDelimitedString(results));
return results.isEmpty() ? "GeoResults [empty]"
: String.format("GeoResults [averageDistance: %s, size: %s]", averageDistance, results.size());
}
private static Distance calculateAverageDistance(List<? extends GeoResult<?>> results, Metric metric) {

View File

@@ -39,22 +39,25 @@ public interface ParameterAccessor extends Iterable<Object> {
* @return the {@link Vector} of the parameters, if available; {@literal null} otherwise.
* @since 4.0
*/
@Nullable
Vector getVector();
default @Nullable Vector getVector() {
return null;
}
/**
* @return the {@link Score} of the parameters, if available; {@literal null} otherwise.
* @since 4.0
*/
@Nullable
Score getScore();
default @Nullable Score getScore() {
return null;
}
/**
* @return the {@link Range} of {@link Score} of the parameters, if available; {@literal null} otherwise.
* @since 4.0
*/
@Nullable
Range<Score> getScoreRange();
default @Nullable Range<Score> getScoreRange() {
return null;
}
/**
* @return the {@link ScrollPosition} of the parameters, if available; {@literal null} otherwise.

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.repository.query;
import static java.lang.String.*;
import static java.lang.String.format;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -226,6 +226,12 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
return vectorIndex != -1;
}
/**
* Returns the index of the {@link Vector} argument.
*
* @return the argument index or {@literal -1} if none defined.
* @since 4.0
*/
public int getVectorIndex() {
return vectorIndex;
}
@@ -240,12 +246,18 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
return scoreIndex != -1;
}
/**
* Returns the index of the {@link Score} argument.
*
* @return the argument index or {@literal -1} if none defined.
* @since 4.0
*/
public int getScoreIndex() {
return scoreIndex;
}
/**
* Returns whether the method the {@link Parameters} was created for contains a {@link Range} of {@link Score}
* Returns whether the method, the {@link Parameters} was created for, contains a {@link Range} of {@link Score}
* argument.
*
* @return
@@ -255,6 +267,12 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
return scoreRangeIndex != -1;
}
/**
* Returns the index of the argument that contains a {@link Range} of {@link Score}.
*
* @return the argument index or {@literal -1} if none defined.
* @since 4.0
*/
public int getScoreRangeIndex() {
return scoreRangeIndex;
}

View File

@@ -33,12 +33,12 @@ class SearchResultUnitTests {
SearchResult<String> third = new SearchResult<>("Bar", Score.of(2.5));
SearchResult<String> fourth = new SearchResult<>("Foo", Score.of(5.2));
@Test // GH-
@Test // GH-3285
void considersSameInstanceEqual() {
assertThat(first.equals(first)).isTrue();
}
@Test // GH-
@Test // GH-3285
void considersSameValuesAsEqual() {
assertThat(first.equals(second)).isTrue();
@@ -49,14 +49,13 @@ class SearchResultUnitTests {
assertThat(fourth.equals(first)).isFalse();
}
@Test
@Test // GH-3285
@SuppressWarnings({ "rawtypes", "unchecked" })
// GH-
void rejectsNullContent() {
assertThatIllegalArgumentException().isThrownBy(() -> new SearchResult(null, Score.of(2.5)));
}
@Test // GH-
@Test // GH-3285
@SuppressWarnings("unchecked")
void testSerialization() {

View File

@@ -33,7 +33,7 @@ import org.springframework.util.SerializationUtils;
class SearchResultsUnitTests {
@SuppressWarnings("unchecked")
@Test // GH-
@Test // GH-3285
void testSerialization() {
var result = new SearchResult<>("test", Score.of(2));
@@ -45,7 +45,7 @@ class SearchResultsUnitTests {
}
@SuppressWarnings("unchecked")
@Test // GH-
@Test // GH-3285
void testStream() {
var result = new SearchResult<>("test", Score.of(2));
@@ -56,7 +56,7 @@ class SearchResultsUnitTests {
}
@SuppressWarnings("unchecked")
@Test // GH-
@Test // GH-3285
void testContentStream() {
var result = new SearchResult<>("test", Score.of(2));

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.data.domain;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import org.junit.jupiter.api.Test;
@@ -26,14 +27,14 @@ import org.junit.jupiter.api.Test;
*/
class SimilarityUnitTests {
@Test
@Test // GH-3285
void shouldBeBounded() {
assertThatIllegalArgumentException().isThrownBy(() -> Similarity.of(-1));
assertThatIllegalArgumentException().isThrownBy(() -> Similarity.of(1.01));
}
@Test
@Test // GH-3285
void shouldConstructRawSimilarity() {
Similarity similarity = Similarity.raw(2, ScoringFunction.unspecified());
@@ -41,7 +42,7 @@ class SimilarityUnitTests {
assertThat(similarity.getValue()).isEqualTo(2);
}
@Test
@Test // GH-3285
void shouldConstructGenericSimilarity() {
Similarity similarity = Similarity.of(1);
@@ -51,7 +52,7 @@ class SimilarityUnitTests {
assertThat(similarity.getFunction()).isEqualTo(ScoringFunction.unspecified());
}
@Test
@Test // GH-3285
void shouldConstructMeteredSimilarity() {
Similarity similarity = Similarity.of(1, VectorScoringFunctions.COSINE);
@@ -62,7 +63,7 @@ class SimilarityUnitTests {
assertThat(similarity.getFunction()).isEqualTo(VectorScoringFunctions.COSINE);
}
@Test
@Test // GH-3285
void shouldConstructRange() {
Range<Similarity> range = Similarity.between(0.5, 1);
@@ -74,7 +75,7 @@ class SimilarityUnitTests {
assertThat(range.getUpperBound().isInclusive()).isTrue();
}
@Test
@Test // GH-3285
void shouldConstructRangeWithFunction() {
Range<Similarity> range = Similarity.between(0.5, 1, VectorScoringFunctions.COSINE);

View File

@@ -128,7 +128,7 @@ class SimpleParameterAccessorUnitTests {
assertThat(accessor.getSort()).isEqualTo(sort);
}
@Test
@Test // GH-3285
void returnsScoreIfAvailable() {
Score score = Score.of(1);
@@ -137,7 +137,7 @@ class SimpleParameterAccessorUnitTests {
assertThat(accessor.getScore()).isEqualTo(score);
}
@Test
@Test // GH-3285
void returnsScoreRangeIfAvailable() {
Range<Similarity> range = Similarity.between(0, 1);