refactor: Add support for OffsetDateTime and LocalTime properties.

Recent drivers support them without issues and don’t need separate conversions. Closes #2706.
This commit is contained in:
Michael Simons
2023-04-11 13:24:14 +02:00
parent 4fc80a3d90
commit 8c05a6bfee
5 changed files with 108 additions and 4 deletions

View File

@@ -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
* <a href="https://neo4j.com/docs/driver-manual/current/cypher-values/">Working with Cypher values</a>.
* <a href="https://neo4j.com/docs/java-manual/current/cypher-workflow/#java-driver-type-mapping">Working with Cypher values</a>.
*
* @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));

View File

@@ -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
* <a href="See https://neo4j.com/docs/driver-manual/1.7/cypher-values/#driver-neo4j-type-system" />
* <a href="https://neo4j.com/docs/driver-manual/1.7/cypher-values/#driver-neo4j-type-system" />
*/
private static final Set<Class<?>> COMPARABLE_TEMPORAL_TYPES;
static {
Set<Class<?>> 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);
}

View File

@@ -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;
@@ -133,6 +137,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;
@@ -4372,6 +4377,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.
@@ -4701,6 +4726,15 @@ class RepositoryIT {
interface EntityWithCustomIdAndDynamicLabelsRepository
extends Neo4jRepository<EntitiesWithDynamicLabels.EntityWithCustomIdAndDynamicLabels, String> {}
interface TemporalRepository extends
Neo4jRepository<OffsetTemporalEntity, UUID> {
List<OffsetTemporalEntity> findAllByProperty1After(OffsetDateTime aValue);
List<OffsetTemporalEntity> findAllByProperty2After(LocalTime aValue);
}
@SpringJUnitConfig(Config.class)
static abstract class IntegrationTestBase {

View File

@@ -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;
}
}

View File

@@ -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