Browse Source

#423 - Add r2dbc-postgresql Geotypes to simple types.

R2DBC Postgres Geo-types are now considered simple types that are passed-thru to the driver without further mapping. Types such as io.r2dbc.postgresql.codec.Circle or io.r2dbc.postgresql.codec.Box can be used directly in domain models and as bind parameters.
pull/1188/head
Mark Paluch 5 years ago
parent
commit
0868f2aaef
No known key found for this signature in database
GPG Key ID: 51A00FA751B91849
  1. 4
      pom.xml
  2. 170
      src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java
  3. 85
      src/test/java/org/springframework/data/r2dbc/core/PostgresIntegrationTests.java
  4. 2
      src/test/java/org/springframework/data/r2dbc/testing/PostgresTestSupport.java

4
pom.xml

@ -35,7 +35,7 @@ @@ -35,7 +35,7 @@
<r2dbc-spi-test.version>0.8.0.RELEASE</r2dbc-spi-test.version>
<mssql-jdbc.version>7.1.2.jre8-preview</mssql-jdbc.version>
<mariadb-jdbc.version>2.5.4</mariadb-jdbc.version>
<r2dbc-releasetrain.version>Arabba-SR6</r2dbc-releasetrain.version>
<r2dbc-releasetrain.version>Arabba-BUILD-SNAPSHOT</r2dbc-releasetrain.version>
<reactive-streams.version>1.0.3</reactive-streams.version>
<netty>4.1.47.Final</netty>
</properties>
@ -221,7 +221,7 @@ @@ -221,7 +221,7 @@
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-postgresql</artifactId>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>

170
src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java

@ -3,15 +3,28 @@ package org.springframework.data.r2dbc.dialect; @@ -3,15 +3,28 @@ package org.springframework.data.r2dbc.dialect;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.relational.core.dialect.ArrayColumns;
import org.springframework.data.util.Lazy;
import org.springframework.lang.NonNull;
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
import org.springframework.util.ClassUtils;
@ -25,15 +38,25 @@ public class PostgresDialect extends org.springframework.data.relational.core.di @@ -25,15 +38,25 @@ public class PostgresDialect extends org.springframework.data.relational.core.di
private static final Set<Class<?>> SIMPLE_TYPES;
private static final boolean GEO_TYPES_PRESENT = ClassUtils.isPresent("io.r2dbc.postgresql.codec.Polygon",
PostgresDialect.class.getClassLoader());
static {
Set<Class<?>> simpleTypes = new HashSet<>(Arrays.asList(UUID.class, URL.class, URI.class, InetAddress.class));
if (ClassUtils.isPresent("io.r2dbc.postgresql.codec.Json", PostgresDialect.class.getClassLoader())) {
// conditional Postgres JSON support.
ifClassPresent("io.r2dbc.postgresql.codec.Json", simpleTypes::add);
simpleTypes
.add(ClassUtils.resolveClassName("io.r2dbc.postgresql.codec.Json", PostgresDialect.class.getClassLoader()));
}
// conditional Postgres Geo support.
Stream.of("io.r2dbc.postgresql.codec.Box", //
"io.r2dbc.postgresql.codec.Circle", //
"io.r2dbc.postgresql.codec.Line", //
"io.r2dbc.postgresql.codec.Lseg", //
"io.r2dbc.postgresql.codec.Point", //
"io.r2dbc.postgresql.codec.Path", //
"io.r2dbc.postgresql.codec.Polygon") //
.forEach(s -> ifClassPresent(s, simpleTypes::add));
SIMPLE_TYPES = simpleTypes;
}
@ -76,6 +99,23 @@ public class PostgresDialect extends org.springframework.data.relational.core.di @@ -76,6 +99,23 @@ public class PostgresDialect extends org.springframework.data.relational.core.di
return this.arrayColumns.get();
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.dialect.Dialect#getConverters()
*/
@Override
public Collection<Object> getConverters() {
if (GEO_TYPES_PRESENT) {
return Arrays.asList(FromPostgresPointConverter.INSTANCE, ToPostgresPointConverter.INSTANCE, //
FromPostgresCircleConverter.INSTANCE, ToPostgresCircleConverter.INSTANCE, //
FromPostgresBoxConverter.INSTANCE, ToPostgresBoxConverter.INSTANCE, //
FromPostgresPolygonConverter.INSTANCE, ToPostgresPolygonConverter.INSTANCE);
}
return Collections.emptyList();
}
private static class R2dbcArrayColumns implements ArrayColumns {
private final ArrayColumns delegate;
@ -107,4 +147,126 @@ public class PostgresDialect extends org.springframework.data.relational.core.di @@ -107,4 +147,126 @@ public class PostgresDialect extends org.springframework.data.relational.core.di
}
}
/**
* If the class is present on the class path, invoke the specified consumer {@code action} with the class object,
* otherwise do nothing.
*
* @param action block to be executed if a value is present.
*/
private static void ifClassPresent(String className, Consumer<Class<?>> action) {
if (ClassUtils.isPresent(className, PostgresDialect.class.getClassLoader())) {
action.accept(ClassUtils.resolveClassName(className, PostgresDialect.class.getClassLoader()));
}
}
@ReadingConverter
private enum FromPostgresBoxConverter implements Converter<io.r2dbc.postgresql.codec.Box, Box> {
INSTANCE;
@Override
public Box convert(io.r2dbc.postgresql.codec.Box source) {
return new Box(FromPostgresPointConverter.INSTANCE.convert(source.getA()),
FromPostgresPointConverter.INSTANCE.convert(source.getB()));
}
}
@WritingConverter
private enum ToPostgresBoxConverter implements Converter<Box, io.r2dbc.postgresql.codec.Box> {
INSTANCE;
@Override
public io.r2dbc.postgresql.codec.Box convert(Box source) {
return io.r2dbc.postgresql.codec.Box.of(ToPostgresPointConverter.INSTANCE.convert(source.getFirst()),
ToPostgresPointConverter.INSTANCE.convert(source.getSecond()));
}
}
@ReadingConverter
private enum FromPostgresCircleConverter implements Converter<io.r2dbc.postgresql.codec.Circle, Circle> {
INSTANCE;
@Override
public Circle convert(io.r2dbc.postgresql.codec.Circle source) {
return new Circle(source.getCenter().getX(), source.getCenter().getY(), source.getRadius());
}
}
@WritingConverter
private enum ToPostgresCircleConverter implements Converter<Circle, io.r2dbc.postgresql.codec.Circle> {
INSTANCE;
@Override
public io.r2dbc.postgresql.codec.Circle convert(Circle source) {
return io.r2dbc.postgresql.codec.Circle.of(source.getCenter().getX(), source.getCenter().getY(),
source.getRadius().getValue());
}
}
@ReadingConverter
private enum FromPostgresPolygonConverter implements Converter<io.r2dbc.postgresql.codec.Polygon, Polygon> {
INSTANCE;
@Override
public Polygon convert(io.r2dbc.postgresql.codec.Polygon source) {
List<io.r2dbc.postgresql.codec.Point> sourcePoints = source.getPoints();
List<Point> targetPoints = new ArrayList<>(sourcePoints.size());
for (io.r2dbc.postgresql.codec.Point sourcePoint : sourcePoints) {
targetPoints.add(FromPostgresPointConverter.INSTANCE.convert(sourcePoint));
}
return new Polygon(targetPoints);
}
}
@WritingConverter
private enum ToPostgresPolygonConverter implements Converter<Polygon, io.r2dbc.postgresql.codec.Polygon> {
INSTANCE;
@Override
public io.r2dbc.postgresql.codec.Polygon convert(Polygon source) {
List<Point> sourcePoints = source.getPoints();
List<io.r2dbc.postgresql.codec.Point> targetPoints = new ArrayList<>(sourcePoints.size());
for (Point sourcePoint : sourcePoints) {
targetPoints.add(ToPostgresPointConverter.INSTANCE.convert(sourcePoint));
}
return io.r2dbc.postgresql.codec.Polygon.of(targetPoints);
}
}
@ReadingConverter
private enum FromPostgresPointConverter implements Converter<io.r2dbc.postgresql.codec.Point, Point> {
INSTANCE;
@Override
@NonNull
public Point convert(io.r2dbc.postgresql.codec.Point source) {
return new Point(source.getX(), source.getY());
}
}
@WritingConverter
private enum ToPostgresPointConverter implements Converter<Point, io.r2dbc.postgresql.codec.Point> {
INSTANCE;
@Override
@NonNull
public io.r2dbc.postgresql.codec.Point convert(Point source) {
return io.r2dbc.postgresql.codec.Point.of(source.getX(), source.getY());
}
}
}

85
src/test/java/org/springframework/data/r2dbc/core/PostgresIntegrationTests.java

@ -20,7 +20,14 @@ import static org.springframework.data.relational.core.query.Criteria.*; @@ -20,7 +20,14 @@ import static org.springframework.data.relational.core.query.Criteria.*;
import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
import io.r2dbc.postgresql.PostgresqlConnectionFactory;
import io.r2dbc.postgresql.codec.Box;
import io.r2dbc.postgresql.codec.Circle;
import io.r2dbc.postgresql.codec.EnumCodec;
import io.r2dbc.postgresql.codec.Line;
import io.r2dbc.postgresql.codec.Lseg;
import io.r2dbc.postgresql.codec.Path;
import io.r2dbc.postgresql.codec.Point;
import io.r2dbc.postgresql.codec.Polygon;
import io.r2dbc.postgresql.extension.CodecRegistrar;
import io.r2dbc.spi.ConnectionFactory;
import lombok.AllArgsConstructor;
@ -36,7 +43,6 @@ import javax.sql.DataSource; @@ -36,7 +43,6 @@ import javax.sql.DataSource;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.dao.DataAccessException;
@ -133,7 +139,6 @@ public class PostgresIntegrationTests extends R2dbcIntegrationTestSupport { @@ -133,7 +139,6 @@ public class PostgresIntegrationTests extends R2dbcIntegrationTestSupport {
}
@Test // gh-411
@Ignore("Depends on https://github.com/pgjdbc/r2dbc-postgresql/issues/301")
public void shouldWriteAndReadEnumValuesUsingDriverInternals() {
CodecRegistrar codecRegistrar = EnumCodec.builder().withEnum("state_enum", State.class).build();
@ -183,6 +188,63 @@ public class PostgresIntegrationTests extends R2dbcIntegrationTestSupport { @@ -183,6 +188,63 @@ public class PostgresIntegrationTests extends R2dbcIntegrationTestSupport {
}
@Test // gh-423
public void shouldReadAndWriteGeoTypes() {
GeoType geoType = new GeoType();
geoType.thePoint = Point.of(1, 2);
geoType.theBox = Box.of(Point.of(3, 4), Point.of(1, 2));
geoType.theCircle = Circle.of(1, 2, 3);
geoType.theLine = Line.of(1, 2, 3, 4);
geoType.theLseg = Lseg.of(Point.of(1, 2), Point.of(3, 4));
geoType.thePath = Path.open(Point.of(1, 2), Point.of(3, 4));
geoType.thePolygon = Polygon.of(Point.of(1, 2), Point.of(3, 4), Point.of(5, 6), Point.of(1, 2));
geoType.springDataBox = new org.springframework.data.geo.Box(new org.springframework.data.geo.Point(3, 4),
new org.springframework.data.geo.Point(1, 2));
geoType.springDataCircle = new org.springframework.data.geo.Circle(1, 2, 3);
geoType.springDataPoint = new org.springframework.data.geo.Point(1, 2);
geoType.springDataPolygon = new org.springframework.data.geo.Polygon(new org.springframework.data.geo.Point(1, 2),
new org.springframework.data.geo.Point(3, 4), new org.springframework.data.geo.Point(5, 6),
new org.springframework.data.geo.Point(1, 2));
template.execute("DROP TABLE IF EXISTS geo_type");
template.execute("CREATE TABLE geo_type (" //
+ "id serial PRIMARY KEY," //
+ "the_point POINT," //
+ "the_box BOX," //
+ "the_circle CIRCLE," //
+ "the_line LINE," //
+ "the_lseg LSEG," //
+ "the_path PATH," //
+ "the_polygon POLYGON," //
+ "spring_data_box BOX," //
+ "spring_data_circle CIRCLE," //
+ "spring_data_point POINT," //
+ "spring_data_polygon POLYGON" //
+ ")");
R2dbcEntityTemplate template = new R2dbcEntityTemplate(client,
new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE));
GeoType saved = template.insert(geoType).block();
GeoType loaded = template.select(Query.empty(), GeoType.class) //
.blockLast();
assertThat(saved.id).isEqualTo(loaded.id);
assertThat(saved.thePoint).isEqualTo(loaded.thePoint);
assertThat(saved.theBox).isEqualTo(loaded.theBox);
assertThat(saved.theCircle).isEqualTo(loaded.theCircle);
assertThat(saved.theLine).isEqualTo(loaded.theLine);
assertThat(saved.theLseg).isEqualTo(loaded.theLseg);
assertThat(saved.thePath).isEqualTo(loaded.thePath);
assertThat(saved.thePolygon).isEqualTo(loaded.thePolygon);
assertThat(saved.springDataBox).isEqualTo(loaded.springDataBox);
assertThat(saved.springDataCircle).isEqualTo(loaded.springDataCircle);
assertThat(saved.springDataPoint).isEqualTo(loaded.springDataPoint);
assertThat(saved.springDataPolygon).isEqualTo(loaded.springDataPolygon);
assertThat(saved).isEqualTo(loaded);
}
private void insert(EntityWithArrays object) {
client.insert() //
@ -219,4 +281,23 @@ public class PostgresIntegrationTests extends R2dbcIntegrationTestSupport { @@ -219,4 +281,23 @@ public class PostgresIntegrationTests extends R2dbcIntegrationTestSupport {
int[][] multidimensionalArray;
List<Integer> collectionArray;
}
@Data
static class GeoType {
@Id Integer id;
Point thePoint;
Box theBox;
Circle theCircle;
Line theLine;
Lseg theLseg;
Path thePath;
Polygon thePolygon;
org.springframework.data.geo.Box springDataBox;
org.springframework.data.geo.Circle springDataCircle;
org.springframework.data.geo.Point springDataPoint;
org.springframework.data.geo.Polygon springDataPolygon;
}
}

2
src/test/java/org/springframework/data/r2dbc/testing/PostgresTestSupport.java

@ -81,7 +81,7 @@ public class PostgresTestSupport { @@ -81,7 +81,7 @@ public class PostgresTestSupport {
.database("postgres") //
.username("postgres") //
.password("") //
.jdbcUrl("jdbc:postgresql://localhost/postgres") //
.jdbcUrl("jdbc:postgresql://localhost:5432/postgres") //
.build();
}

Loading…
Cancel
Save