From 83c9aa7c5fd3ede9311c40521cfb1be41f0fbe09 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Tue, 11 Apr 2023 13:24:14 +0200 Subject: [PATCH] refactor: Add support for `OffsetDateTime` and `LocalTime` properties. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recent drivers support them without issues and don’t need separate conversions. Closes #2706. --- .../data/neo4j/core/convert/CypherTypes.java | 4 +- .../neo4j/repository/query/PartValidator.java | 6 +- .../integration/imperative/RepositoryIT.java | 34 ++++++++++ .../shared/common/OffsetTemporalEntity.java | 66 +++++++++++++++++++ .../support/Neo4jRepositoryFactoryTest.java | 2 +- 5 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/springframework/data/neo4j/integration/shared/common/OffsetTemporalEntity.java diff --git a/src/main/java/org/springframework/data/neo4j/core/convert/CypherTypes.java b/src/main/java/org/springframework/data/neo4j/core/convert/CypherTypes.java index 0ca063c34..c42801dde 100644 --- a/src/main/java/org/springframework/data/neo4j/core/convert/CypherTypes.java +++ b/src/main/java/org/springframework/data/neo4j/core/convert/CypherTypes.java @@ -18,6 +18,7 @@ package org.springframework.data.neo4j.core.convert; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -32,7 +33,7 @@ import org.springframework.data.convert.ConverterBuilder; /** * Conversions for all known Cypher types, directly supported by the driver. See - * Working with Cypher values. + * Working with Cypher values. * * @author Michael J. Simons * @since 6.0 @@ -56,6 +57,7 @@ final class CypherTypes { hlp.add(ConverterBuilder.reading(Value.class, byte[].class, Value::asByteArray).andWriting(Values::value)); hlp.add(ConverterBuilder.reading(Value.class, LocalDate.class, Value::asLocalDate).andWriting(Values::value)); hlp.add(ConverterBuilder.reading(Value.class, OffsetTime.class, Value::asOffsetTime).andWriting(Values::value)); + hlp.add(ConverterBuilder.reading(Value.class, OffsetDateTime.class, Value::asOffsetDateTime).andWriting(Values::value)); hlp.add(ConverterBuilder.reading(Value.class, LocalTime.class, Value::asLocalTime).andWriting(Values::value)); hlp.add(ConverterBuilder.reading(Value.class, ZonedDateTime.class, Value::asZonedDateTime).andWriting(Values::value)); hlp.add(ConverterBuilder.reading(Value.class, LocalDateTime.class, Value::asLocalDateTime).andWriting(Values::value)); diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/PartValidator.java b/src/main/java/org/springframework/data/neo4j/repository/query/PartValidator.java index de086143b..e4364cc09 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/PartValidator.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/PartValidator.java @@ -18,6 +18,8 @@ package org.springframework.data.neo4j.repository.query; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.ZonedDateTime; import java.util.Arrays; @@ -50,12 +52,12 @@ class PartValidator { /** * A set of the temporal types that are directly passable to the driver and support a meaningful comparison in a * temporal sense (after, before). See - * + * */ private static final Set> COMPARABLE_TEMPORAL_TYPES; static { Set> hlp = new TreeSet<>(Comparator.comparing(Class::getName)); - hlp.addAll(Arrays.asList(LocalDate.class, OffsetTime.class, ZonedDateTime.class, LocalDateTime.class, Instant.class)); + hlp.addAll(Arrays.asList(LocalDate.class, OffsetTime.class, OffsetDateTime.class, LocalTime.class, ZonedDateTime.class, LocalDateTime.class, Instant.class)); COMPARABLE_TEMPORAL_TYPES = Collections.unmodifiableSet(hlp); } diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java index 0f9616000..8bdb5ba0f 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java @@ -24,6 +24,9 @@ import static org.assertj.core.api.Assertions.tuple; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -36,6 +39,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.TimeZone; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Function; @@ -127,6 +131,7 @@ import org.springframework.data.neo4j.integration.shared.common.Inheritance; import org.springframework.data.neo4j.integration.shared.common.KotlinPerson; import org.springframework.data.neo4j.integration.shared.common.LikesHobbyRelationship; import org.springframework.data.neo4j.integration.shared.common.MultipleLabels; +import org.springframework.data.neo4j.integration.shared.common.OffsetTemporalEntity; import org.springframework.data.neo4j.integration.shared.common.OneToOneSource; import org.springframework.data.neo4j.integration.shared.common.OneToOneTarget; import org.springframework.data.neo4j.integration.shared.common.ParentNode; @@ -4332,6 +4337,26 @@ class RepositoryIT { } } + @Test // GH-2706 + void findByOffsetDateTimeShouldWork(@Autowired TemporalRepository temporalRepository) { + + temporalRepository.deleteAll(); + + LocalDateTime fixedDateTime = LocalDateTime.of(2023, 1, 1, 21, 21, 0); + ZoneId europeBerlin = TimeZone.getTimeZone("Europe/Berlin").toZoneId(); + OffsetDateTime v1 = OffsetDateTime.of(fixedDateTime, europeBerlin.getRules().getOffset(fixedDateTime)); + LocalTime v2 = fixedDateTime.toLocalTime(); + + temporalRepository.save(new OffsetTemporalEntity(v1, v2)); + temporalRepository.save(new OffsetTemporalEntity(v1.minusDays(2), v2.minusMinutes(2))); + + assertThat(temporalRepository.findAllByProperty1After(v1)).isEmpty(); + assertThat(temporalRepository.findAllByProperty2After(v2)).isEmpty(); + + assertThat(temporalRepository.findAllByProperty1After(v1.minusDays(1))).hasSize(1); + assertThat(temporalRepository.findAllByProperty2After(v2.minusMinutes(1))).hasSize(1); + } + /** * The tests in this class ensure that in case of an inheritance scenario no DTO is projected but the extending class * is used. If it wasn't the case, we wouldn't find the relationship nor the other attribute. @@ -4661,6 +4686,15 @@ class RepositoryIT { interface EntityWithCustomIdAndDynamicLabelsRepository extends Neo4jRepository {} + interface TemporalRepository extends + Neo4jRepository { + + List findAllByProperty1After(OffsetDateTime aValue); + + List findAllByProperty2After(LocalTime aValue); + + } + @SpringJUnitConfig(Config.class) static abstract class IntegrationTestBase { diff --git a/src/test/java/org/springframework/data/neo4j/integration/shared/common/OffsetTemporalEntity.java b/src/test/java/org/springframework/data/neo4j/integration/shared/common/OffsetTemporalEntity.java new file mode 100644 index 000000000..c9d0bada4 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/shared/common/OffsetTemporalEntity.java @@ -0,0 +1,66 @@ +/* + * Copyright 2011-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.shared.common; + +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.util.UUID; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +/** + * Using some offset temporal types. + * + * @author Michael J. Simons + */ +@Node +public class OffsetTemporalEntity { + + @Id + @GeneratedValue + private UUID uuid; + + private OffsetDateTime property1; + + private LocalTime property2; + + public OffsetTemporalEntity(OffsetDateTime property1, LocalTime property2) { + this.property1 = property1; + this.property2 = property2; + } + + public UUID getUuid() { + return uuid; + } + + public OffsetDateTime getProperty1() { + return property1; + } + + public void setProperty1(OffsetDateTime property1) { + this.property1 = property1; + } + + public LocalTime getProperty2() { + return property2; + } + + public void setProperty2(LocalTime property2) { + this.property2 = property2; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryTest.java b/src/test/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryTest.java index 28f62917c..8a02e3d80 100644 --- a/src/test/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryTest.java +++ b/src/test/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryTest.java @@ -122,7 +122,7 @@ class Neo4jRepositoryFactoryTest { void validateTemporalShouldWork() { assertThatExceptionOfType(QueryCreationException.class).isThrownBy(() -> repositoryFactory.getRepository(InvalidTemporal.class)) - .withMessageMatching("Could not create query for .*: The keywords \\[IsAfter, After] work only with properties with one of the following types: \\[class java.time.Instant, class java.time.LocalDate, class java.time.LocalDateTime, class java.time.OffsetTime, class java.time.ZonedDateTime]"); + .withMessageMatching("Could not create query for .*: The keywords \\[IsAfter, After] work only with properties with one of the following types: \\[class java.time.Instant, class java.time.LocalDate, class java.time.LocalDateTime, class java.time.LocalTime, class java.time.OffsetDateTime, class java.time.OffsetTime, class java.time.ZonedDateTime]"); } @Test