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:
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user