Browse Source

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
pull/3304/head
Christoph Strobl 8 months ago committed by Mark Paluch
parent
commit
9a7ea62b2e
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 12
      src/main/antora/modules/ROOT/pages/repositories/vector-search.adoc
  2. 1
      src/main/java/org/springframework/data/domain/SearchResult.java
  3. 4
      src/main/java/org/springframework/data/domain/SearchResults.java
  4. 3
      src/main/java/org/springframework/data/geo/GeoResult.java
  5. 7
      src/main/java/org/springframework/data/geo/GeoResults.java
  6. 15
      src/main/java/org/springframework/data/repository/query/ParameterAccessor.java
  7. 22
      src/main/java/org/springframework/data/repository/query/Parameters.java
  8. 9
      src/test/java/org/springframework/data/domain/SearchResultUnitTests.java
  9. 6
      src/test/java/org/springframework/data/domain/SearchResultsUnitTests.java
  10. 15
      src/test/java/org/springframework/data/domain/SimilarityUnitTests.java
  11. 4
      src/test/java/org/springframework/data/repository/query/SimpleParameterAccessorUnitTests.java

12
src/main/antora/modules/ROOT/pages/repositories/vector-search.adoc

@ -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`. 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. 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. 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. 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. 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. 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. 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 * Query Derivation
* Declaring a String-based Query * 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]] [[vector-search.method.derivation]]
=== Derived Search Methods === 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. 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. 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]] [[vector-search.method.sorting]]
=== Sorting === Sorting

1
src/main/java/org/springframework/data/domain/SearchResult.java

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

4
src/main/java/org/springframework/data/domain/SearchResults.java

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

3
src/main/java/org/springframework/data/geo/GeoResult.java

@ -19,7 +19,6 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -79,6 +78,6 @@ public final class GeoResult<T> implements Serializable {
@Override @Override
public String toString() { 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);
} }
} }

7
src/main/java/org/springframework/data/geo/GeoResults.java

@ -15,7 +15,6 @@
*/ */
package org.springframework.data.geo; package org.springframework.data.geo;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collections; import java.util.Collections;
@ -23,11 +22,9 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.data.annotation.PersistenceCreator; import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; 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. * 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 @Override
public String toString() { public String toString() {
return String.format("GeoResults: [averageDistance: %s, results: %s]", averageDistance.toString(), return results.isEmpty() ? "GeoResults [empty]"
StringUtils.collectionToCommaDelimitedString(results)); : String.format("GeoResults [averageDistance: %s, size: %s]", averageDistance, results.size());
} }
private static Distance calculateAverageDistance(List<? extends GeoResult<?>> results, Metric metric) { private static Distance calculateAverageDistance(List<? extends GeoResult<?>> results, Metric metric) {

15
src/main/java/org/springframework/data/repository/query/ParameterAccessor.java

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

22
src/main/java/org/springframework/data/repository/query/Parameters.java

@ -15,7 +15,7 @@
*/ */
package org.springframework.data.repository.query; package org.springframework.data.repository.query;
import static java.lang.String.*; import static java.lang.String.format;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
@ -226,6 +226,12 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
return vectorIndex != -1; 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() { public int getVectorIndex() {
return vectorIndex; return vectorIndex;
} }
@ -240,12 +246,18 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
return scoreIndex != -1; 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() { public int getScoreIndex() {
return scoreIndex; 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. * argument.
* *
* @return * @return
@ -255,6 +267,12 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
return scoreRangeIndex != -1; 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() { public int getScoreRangeIndex() {
return scoreRangeIndex; return scoreRangeIndex;
} }

9
src/test/java/org/springframework/data/domain/SearchResultUnitTests.java

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

6
src/test/java/org/springframework/data/domain/SearchResultsUnitTests.java

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

15
src/test/java/org/springframework/data/domain/SimilarityUnitTests.java

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

4
src/test/java/org/springframework/data/repository/query/SimpleParameterAccessorUnitTests.java

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

Loading…
Cancel
Save