diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java index 815008b3..4e677eb4 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java @@ -945,8 +945,22 @@ private Object parseDocumentResult(redis.clients.jedis.search.Document doc) { jsonBuilder.append("\"").append(fieldName).append("\":"); + // Check if this field is a Point type in the domain class + boolean isPointField = false; + try { + Field domainField = ReflectionUtils.findField(domainType, fieldName); + if (domainField != null && domainField.getType() == Point.class) { + isPointField = true; + } + } catch (Exception e) { + // Ignore - field might not exist in projection + } + // Handle different types based on the raw value from Redis - if (fieldName.equals("name") || (valueStr.startsWith("\"") && valueStr.endsWith("\""))) { + if (isPointField && valueStr.contains(",") && !valueStr.startsWith("\"")) { + // Point field - stored as "lon,lat" in Redis, needs to be quoted for PointTypeAdapter + jsonBuilder.append("\"").append(valueStr).append("\""); + } else if (fieldName.equals("name") || (valueStr.startsWith("\"") && valueStr.endsWith("\""))) { // String field - quote if not already quoted if (valueStr.startsWith("\"") && valueStr.endsWith("\"")) { jsonBuilder.append(valueStr); diff --git a/tests/src/test/java/com/redis/om/spring/annotations/document/PointProjectionTest.java b/tests/src/test/java/com/redis/om/spring/annotations/document/PointProjectionTest.java new file mode 100644 index 00000000..c270edd3 --- /dev/null +++ b/tests/src/test/java/com/redis/om/spring/annotations/document/PointProjectionTest.java @@ -0,0 +1,98 @@ +package com.redis.om.spring.annotations.document; + +import com.redis.om.spring.AbstractBaseDocumentTest; +import com.redis.om.spring.fixtures.document.model.FlightWithLocation; +import com.redis.om.spring.fixtures.document.repository.FlightWithLocationRepository; +import com.redis.om.spring.fixtures.document.repository.FlightWithLocationRepository.FlightProjection; +import com.redis.om.spring.fixtures.document.repository.FlightWithLocationRepository.FlightProjectionWithoutPoint; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.geo.Point; + +import java.util.NoSuchElementException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Test for issue #661 - NoSuchElementException when using Point in projection interface + * https://github.com/redis/redis-om-spring/issues/661 + */ +class PointProjectionTest extends AbstractBaseDocumentTest { + + @Autowired + private FlightWithLocationRepository repository; + + @BeforeEach + void setup() { + repository.deleteAll(); + + // Create test data + FlightWithLocation flight1 = new FlightWithLocation("AA123", "Flight to Paris", new Point(2.3522, 48.8566)); + FlightWithLocation flight2 = new FlightWithLocation("BA456", "Flight to London", new Point(-0.1276, 51.5074)); + FlightWithLocation flight3 = new FlightWithLocation("LH789", "Flight to Berlin", new Point(13.4050, 52.5200)); + + repository.save(flight1); + repository.save(flight2); + repository.save(flight3); + } + + @Test + void testProjectionWithoutPointWorks() { + // This should work fine - projection without Point field + FlightProjectionWithoutPoint projection = repository.findByName("Flight to Paris"); + + assertThat(projection).isNotNull(); + assertThat(projection.getNumber()).isEqualTo("AA123"); + assertThat(projection.getName()).isEqualTo("Flight to Paris"); + } + + @Test + void testProjectionWithPointNowWorks() { + // This test verifies that issue #661 has been fixed + // Previously would throw NoSuchElementException, now it works + FlightProjection projection = repository.findByNumber("AA123"); + + assertThat(projection).isNotNull(); + assertThat(projection.getNumber()).isEqualTo("AA123"); + assertThat(projection.getName()).isEqualTo("Flight to Paris"); + + // After fix, accessing Point field in projection should work + Point location = projection.getLocation(); + assertThat(location).isNotNull(); + assertThat(location.getX()).isEqualTo(2.3522); + assertThat(location.getY()).isEqualTo(48.8566); + } + + @Test + void testDirectRepositoryAccessWithPointWorks() { + // Direct repository access should work fine + FlightWithLocation flight = repository.findById( + repository.findAll().iterator().next().getId() + ).orElseThrow(); + + assertThat(flight.getLocation()).isNotNull(); + assertThat(flight.getLocation().getX()).isEqualTo(2.3522); + assertThat(flight.getLocation().getY()).isEqualTo(48.8566); + } + + @Test + void testProjectionWithPointShouldWork() { + // After fix, this should work without throwing exception + FlightProjection projection = repository.findByNumber("BA456"); + + assertThat(projection).isNotNull(); + assertThat(projection.getNumber()).isEqualTo("BA456"); + assertThat(projection.getName()).isEqualTo("Flight to London"); + + // After fix, this should NOT throw exception + assertDoesNotThrow(() -> { + Point location = projection.getLocation(); + assertThat(location).isNotNull(); + assertThat(location.getX()).isEqualTo(-0.1276); + assertThat(location.getY()).isEqualTo(51.5074); + }); + } +} \ No newline at end of file diff --git a/tests/src/test/java/com/redis/om/spring/fixtures/document/model/FlightWithLocation.java b/tests/src/test/java/com/redis/om/spring/fixtures/document/model/FlightWithLocation.java new file mode 100644 index 00000000..22b73c03 --- /dev/null +++ b/tests/src/test/java/com/redis/om/spring/fixtures/document/model/FlightWithLocation.java @@ -0,0 +1,35 @@ +package com.redis.om.spring.fixtures.document.model; + +import com.redis.om.spring.annotations.Document; +import com.redis.om.spring.annotations.Indexed; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.geo.Point; + +/** + * Test model for issue #661 - NoSuchElementException when using Point in projection interface + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Document +public class FlightWithLocation { + @Id + private String id; + + @Indexed + private String number; + + private String name; + + @Indexed + private Point location; + + public FlightWithLocation(String number, String name, Point location) { + this.number = number; + this.name = name; + this.location = location; + } +} \ No newline at end of file diff --git a/tests/src/test/java/com/redis/om/spring/fixtures/document/repository/FlightWithLocationRepository.java b/tests/src/test/java/com/redis/om/spring/fixtures/document/repository/FlightWithLocationRepository.java new file mode 100644 index 00000000..a8ab2380 --- /dev/null +++ b/tests/src/test/java/com/redis/om/spring/fixtures/document/repository/FlightWithLocationRepository.java @@ -0,0 +1,26 @@ +package com.redis.om.spring.fixtures.document.repository; + +import com.redis.om.spring.fixtures.document.model.FlightWithLocation; +import com.redis.om.spring.repository.RedisDocumentRepository; +import org.springframework.data.geo.Point; + +public interface FlightWithLocationRepository extends RedisDocumentRepository { + + // Projection interface for testing issue #661 + interface FlightProjection { + String getNumber(); + String getName(); + Point getLocation(); // This causes NoSuchElementException + } + + // Projection without Point for comparison + interface FlightProjectionWithoutPoint { + String getNumber(); + String getName(); + } + + // Find methods using projections + FlightProjection findByNumber(String number); + + FlightProjectionWithoutPoint findByName(String name); +} \ No newline at end of file