Skip to content

Commit 89d5ce9

Browse files
Francisco Guerreromp911de
authored andcommitted
Implement Circle[] codec support
[pgjdbc#394][pgjdbc#403]
1 parent e911fea commit 89d5ce9

File tree

8 files changed

+252
-14
lines changed

8 files changed

+252
-14
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ Support for the following single-dimensional arrays (read and write):
427427
| [`bytea[]`][psql-bytea-ref] | [**`ByteBuffer[]`**][java-ByteBuffer-ref], [`byte[][]`][java-byte-ref]|
428428
| [`character`][psql-character-ref] | [`String[]`][java-string-ref]|
429429
| [`character varying`][psql-character-ref] | [`String[]`][java-string-ref]|
430+
| [`circle[]`][psql-circle-ref] | **`Circle[]`**|
430431
| [`date[]`][psql-date-ref] | [`LocalDate[]`][java-ld-ref]|
431432
| [`double precision[]`][psql-floating-point-ref] | [**`Double[]`**][java-double-ref], [`Float[]`][java-float-ref], [`Boolean[]`][java-boolean-ref], [`Byte[]`][java-byte-ref], [`Short[]`][java-short-ref], [`Integer[]`][java-integer-ref], [`Long[]`][java-long-ref], [`BigDecimal[]`][java-bigdecimal-ref], [`BigInteger[]`][java-biginteger-ref]|
432433
| [enumerated type arrays][psql-enum-ref] | Client code `Enum[]` types through `EnumCodec`|

src/main/java/io/r2dbc/postgresql/codec/AbstractGeometryCodec.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -154,22 +154,22 @@ private static List<String> tokenizeTextData(String content) {
154154

155155
List<String> tokens = new ArrayList<>();
156156

157-
for (int i = 0, s = 0; i < content.length(); i++) {
157+
int length = content.length();
158+
for (int i = 0, tokenStart = 0; i < length; i++) {
158159

159160
char c = content.charAt(i);
160-
161161
if (c == '(' || c == '[' || c == '<' || c == '{') {
162-
s++;
163-
continue;
164-
}
165-
166-
if (c == ',' || c == ')' || c == ']' || c == '>' || c == '}') {
167-
if (s != i) {
168-
tokens.add(content.substring(s, i));
169-
s = i + 1;
162+
tokenStart++;
163+
} else if (c == ',' || c == ')' || c == ']' || c == '>' || c == '}') {
164+
if (tokenStart != i) {
165+
tokens.add(content.substring(tokenStart, i));
166+
tokenStart = i + 1;
170167
} else {
171-
s++;
168+
tokenStart++;
172169
}
170+
} else if (i == length - 1) {
171+
// for cases where there is no token at the end of the string (i.e. "(1.2,123.1),10")
172+
tokens.add(content.substring(tokenStart));
173173
}
174174
}
175175

src/main/java/io/r2dbc/postgresql/codec/CircleCodec.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
import io.netty.buffer.ByteBufAllocator;
2121

2222
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.CIRCLE;
23+
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.CIRCLE_ARRAY;
2324

2425
/**
2526
* @since 0.8.5
2627
*/
27-
final class CircleCodec extends AbstractGeometryCodec<Circle> {
28+
final class CircleCodec extends AbstractGeometryCodec<Circle> implements ArrayCodecDelegate<Circle> {
2829

2930
CircleCodec(ByteBufAllocator byteBufAllocator) {
3031
super(Circle.class, CIRCLE, byteBufAllocator);
@@ -50,6 +51,16 @@ ByteBuf doEncodeBinary(Circle value) {
5051
.writeDouble(value.getRadius());
5152
}
5253

54+
@Override
55+
public String encodeToText(Circle value) {
56+
return String.format("\"%s\"", value);
57+
}
58+
59+
@Override
60+
public PostgresTypeIdentifier getArrayDataType() {
61+
return CIRCLE_ARRAY;
62+
}
63+
5364
int lengthInBytes() {
5465
return 24;
5566
}

src/main/java/io/r2dbc/postgresql/codec/PostgresqlObjectId.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ public enum PostgresqlObjectId implements Type, PostgresTypeIdentifier {
9999
*/
100100
CIRCLE(718, Circle.class),
101101

102+
/**
103+
* The circle array object id
104+
*/
105+
CIRCLE_ARRAY(719, Circle[].class),
106+
102107
/**
103108
* The date object id.
104109
*/

src/test/java/io/r2dbc/postgresql/AbstractCodecIntegrationTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,23 @@ public Publisher<ByteBuffer> stream() {
195195
testCodec(Blob.class, byteToBlob.apply(new byte[]{}), equality, "BYTEA");
196196
}
197197

198+
@Test
199+
void circleArray() {
200+
testCodec(Circle[].class, new Circle[]{Circle.of(Point.of(1.12, 2.12), 3.12), Circle.of(Point.of(Double.MIN_VALUE, Double.MIN_VALUE), Double.MAX_VALUE)}, "CIRCLE[]");
201+
}
202+
198203
@Test
199204
void circle() {
200205
testCodec(Circle.class, Circle.of(Point.of(1.12, 2.12), 3.12), "CIRCLE");
201206
testCodec(Circle.class, Circle.of(Point.of(Double.MIN_VALUE, Double.MIN_VALUE), Double.MAX_VALUE), "CIRCLE");
202207
}
203208

209+
@Test
210+
void circleTwoDimensionalArray() {
211+
testCodec(Circle[][].class, new Circle[][]{{Circle.of(Point.of(1.12, 2.12), 3.12), Circle.of(Point.of(Double.MIN_VALUE, Double.MIN_VALUE), Double.MAX_VALUE)},
212+
{Circle.of(Point.of(-2.4, -456.2), 20), null}}, "CIRCLE[][]");
213+
}
214+
204215
@Test
205216
void clob() {
206217
testCodec(Clob.class,
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package io.r2dbc.postgresql.codec;
2+
3+
import io.netty.buffer.ByteBuf;
4+
import io.r2dbc.postgresql.client.EncodedParameter;
5+
import io.r2dbc.postgresql.client.ParameterAssert;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.Test;
8+
9+
import static io.r2dbc.postgresql.client.EncodedParameter.NULL_VALUE;
10+
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.CIRCLE;
11+
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.CIRCLE_ARRAY;
12+
import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY;
13+
import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT;
14+
import static io.r2dbc.postgresql.util.ByteBufUtils.encode;
15+
import static io.r2dbc.postgresql.util.TestByteBufAllocator.TEST;
16+
import static org.assertj.core.api.Assertions.assertThat;
17+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
18+
19+
/**
20+
* Unit tests for {@link ArrayCodec<Circle>}.
21+
*/
22+
final class CircleArrayCodecUnitTests {
23+
24+
private static final int dataType = CIRCLE_ARRAY.getObjectId();
25+
26+
private ArrayCodec<Circle> codec;
27+
28+
private final ByteBuf SINGLE_DIM_BINARY_ARRAY = TEST
29+
.buffer()
30+
.writeInt(1) // num of dimensions
31+
.writeInt(0) // flag: has nulls
32+
.writeInt(718) // oid
33+
.writeInt(2) // num of elements
34+
.writeInt(1) // ignore Lower Bound
35+
.writeInt(24) // length of element
36+
.writeDouble(1.2) // center point x
37+
.writeDouble(123.1) // center point y
38+
.writeDouble(10.0) // radius
39+
.writeInt(24) // length of element
40+
.writeDouble(-2.4) // center point x
41+
.writeDouble(-456.2) // center point y
42+
.writeDouble(20.0); // radius
43+
44+
private final ByteBuf TWO_DIM_BINARY_ARRAY = TEST
45+
.buffer()
46+
.writeInt(2) // num of dims
47+
.writeInt(1) // flag: has nulls
48+
.writeInt(718) // oid
49+
.writeInt(2) // dim 1 length
50+
.writeInt(1) // dim 1 lower bound
51+
.writeInt(1) // dim 2 length
52+
.writeInt(1) // dim 2 lower bound
53+
.writeInt(24) // length of element
54+
.writeDouble(1.2) // center point x
55+
.writeDouble(123.1) // center point y
56+
.writeDouble(10.0) // radius
57+
.writeInt(-1); // length of null element
58+
59+
@BeforeEach
60+
void setup() {
61+
codec = new ArrayCodec<>(TEST, CIRCLE_ARRAY, new CircleCodec(TEST), Circle.class);
62+
}
63+
64+
@Test
65+
void decodeItem() {
66+
assertThat(codec.decode(SINGLE_DIM_BINARY_ARRAY, dataType, FORMAT_BINARY, Circle[].class))
67+
.isEqualTo(new Circle[]{Circle.of(Point.of(1.2, 123.1), 10), Circle.of(Point.of(-2.4, -456.2), 20)});
68+
}
69+
70+
@Test
71+
void decodeItem_textArray() {
72+
Circle[] expected = {Circle.of(Point.of(1.2, 123.1), 10), Circle.of(Point.of(-2.4, -456.2), 20)};
73+
assertThat(codec.decode(encode(TEST, "{\"<(1.2, 123.1), 10>\",\"<(-2.4, -456.2), 20>\"}"), dataType, FORMAT_TEXT, Circle[].class))
74+
.isEqualTo(expected);
75+
assertThat(codec.decode(encode(TEST, "{\"((1.2, 123.1), 10)\",\"((-2.4, -456.2), 20)\"}"), dataType, FORMAT_TEXT, Circle[].class))
76+
.isEqualTo(expected);
77+
assertThat(codec.decode(encode(TEST, "{\"(1.2, 123.1), 10\",\"(-2.4, -456.2), 20\"}"), dataType, FORMAT_TEXT, Circle[].class))
78+
.isEqualTo(expected);
79+
assertThat(codec.decode(encode(TEST, "{\"1.2, 123.1, 10\",\"-2.4, -456.2, 20\"}"), dataType, FORMAT_TEXT, Circle[].class))
80+
.isEqualTo(expected);
81+
}
82+
83+
@Test
84+
void decodeItem_emptyArray() {
85+
assertThat(codec.decode(encode(TEST, "{}"), dataType, FORMAT_TEXT, Circle[][].class))
86+
.isEqualTo(new Circle[][]{});
87+
}
88+
89+
@Test
90+
void decodeItem_emptyBinaryArray() {
91+
ByteBuf buf = TEST
92+
.buffer()
93+
.writeInt(0)
94+
.writeInt(0)
95+
.writeInt(718);
96+
97+
assertThat(codec.decode(buf, dataType, FORMAT_BINARY, Circle[][].class))
98+
.isEqualTo(new Circle[][]{});
99+
}
100+
101+
@Test
102+
void decodeItem_expectedLessDimensionsInArray() {
103+
assertThatIllegalArgumentException()
104+
.isThrownBy(() -> codec.decode(encode(TEST, "{{\"((1.2, 123.1), 10)\"}}"), dataType, FORMAT_TEXT, Circle[].class))
105+
.withMessage("Dimensions mismatch: 1 expected, but 2 returned from DB");
106+
}
107+
108+
@Test
109+
void decodeItem_expectedLessDimensionsInBinaryArray() {
110+
assertThatIllegalArgumentException()
111+
.isThrownBy(() -> codec.decode(TWO_DIM_BINARY_ARRAY, dataType, FORMAT_BINARY, Circle[].class))
112+
.withMessage("Dimensions mismatch: 1 expected, but 2 returned from DB");
113+
}
114+
115+
@Test
116+
void decodeItem_expectedMoreDimensionsInArray() {
117+
assertThatIllegalArgumentException()
118+
.isThrownBy(() -> codec.decode(encode(TEST, "{\"1.2, 123.1, 10\",\"-2.4, -456.2, 20\"}"), dataType, FORMAT_TEXT, Circle[][].class))
119+
.withMessage("Dimensions mismatch: 2 expected, but 1 returned from DB");
120+
}
121+
122+
@Test
123+
void decodeItem_expectedMoreDimensionsInBinaryArray() {
124+
assertThatIllegalArgumentException()
125+
.isThrownBy(() -> codec.decode(SINGLE_DIM_BINARY_ARRAY, dataType, FORMAT_BINARY, Circle[][].class))
126+
.withMessage("Dimensions mismatch: 2 expected, but 1 returned from DB");
127+
}
128+
129+
@Test
130+
void decodeItem_twoDimensionalArrayWithNull() {
131+
assertThat(codec.decode(encode(TEST, "{{\"((1.2, 123.1), 10)\"},{NULL}}"), dataType, FORMAT_TEXT, Circle[][].class))
132+
.isEqualTo(new Circle[][]{{Circle.of(Point.of(1.2, 123.1), 10)}, {null}});
133+
}
134+
135+
@Test
136+
void decodeItem_twoDimensionalBinaryArrayWithNull() {
137+
assertThat(codec.decode(TWO_DIM_BINARY_ARRAY, dataType, FORMAT_BINARY, Circle[][].class))
138+
.isEqualTo(new Circle[][]{{Circle.of(Point.of(1.2, 123.1), 10)}, {null}});
139+
}
140+
141+
@Test
142+
@SuppressWarnings({"rawtypes", "unchecked"})
143+
void decodeObject() {
144+
Codec genericCodec = codec;
145+
assertThat(genericCodec.canDecode(CIRCLE_ARRAY.getObjectId(), FORMAT_TEXT, Object.class)).isTrue();
146+
Circle[] expected = {Circle.of(Point.of(1.2, 123.1), 10), Circle.of(Point.of(-2.4, -456.2), 20)};
147+
148+
assertThat(genericCodec.decode(SINGLE_DIM_BINARY_ARRAY, dataType, FORMAT_BINARY, Object.class))
149+
.isEqualTo(expected);
150+
assertThat(genericCodec.decode(encode(TEST, "{\"<(1.2, 123.1), 10>\",\"<(-2.4, -456.2), 20>\"}"), dataType, FORMAT_TEXT, Object.class))
151+
.isEqualTo(expected);
152+
}
153+
154+
@Test
155+
void doCanDecode() {
156+
assertThat(codec.doCanDecode(CIRCLE, FORMAT_TEXT)).isFalse();
157+
assertThat(codec.doCanDecode(CIRCLE_ARRAY, FORMAT_TEXT)).isTrue();
158+
assertThat(codec.doCanDecode(CIRCLE_ARRAY, FORMAT_BINARY)).isTrue();
159+
}
160+
161+
@Test
162+
void doCanDecodeNoType() {
163+
assertThatIllegalArgumentException().isThrownBy(() -> codec.doCanDecode(null, null))
164+
.withMessage("type must not be null");
165+
}
166+
167+
@Test
168+
void encodeArray() {
169+
ParameterAssert.assertThat(codec.encodeArray(() -> encode(TEST, "{\"<(1.2, 123.1), 10>\",\"<(-2.4, -456.2), 20>\"}"), CIRCLE_ARRAY))
170+
.hasFormat(FORMAT_TEXT)
171+
.hasType(CIRCLE_ARRAY.getObjectId())
172+
.hasValue(encode(TEST, "{\"<(1.2, 123.1), 10>\",\"<(-2.4, -456.2), 20>\"}"));
173+
}
174+
175+
@Test
176+
void encodeNull() {
177+
ParameterAssert.assertThat(codec.encodeNull())
178+
.isEqualTo(new EncodedParameter(FORMAT_BINARY, CIRCLE_ARRAY.getObjectId(), NULL_VALUE));
179+
}
180+
181+
}

src/test/java/io/r2dbc/postgresql/codec/CircleCodecUnitTests.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.VARCHAR;
2727
import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY;
2828
import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT;
29+
import static io.r2dbc.postgresql.util.ByteBufUtils.encode;
2930
import static io.r2dbc.postgresql.util.TestByteBufAllocator.TEST;
3031
import static org.assertj.core.api.Assertions.assertThat;
3132
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -35,6 +36,8 @@
3536
*/
3637
final class CircleCodecUnitTests {
3738

39+
private static final int dataType = CIRCLE.getObjectId();
40+
3841
@Test
3942
void constructorNoByteBufAllocator() {
4043
assertThatIllegalArgumentException().isThrownBy(() -> new CircleCodec(null))
@@ -96,14 +99,37 @@ void doEncode() {
9699

97100
ParameterAssert.assertThat(codec.doEncode(Circle.of(Point.of(1.12, 2.12), 3.12)))
98101
.hasFormat(FORMAT_BINARY)
99-
.hasType(CIRCLE.getObjectId())
102+
.hasType(dataType)
100103
.hasValue(circleAsBinary);
101104
}
102105

106+
@Test
107+
void decodeText() {
108+
CircleCodec codec = new CircleCodec(TEST);
109+
110+
// Circles are represented by a center point and radius.
111+
// Values of type circle are specified using any of the following syntaxes:
112+
// < ( x , y ) , r >
113+
// ( ( x , y ) , r )
114+
// ( x , y ) , r
115+
// x , y , r
116+
assertThat(codec.decode(encode(TEST, "<(1.2,123.1),10>"), dataType, FORMAT_TEXT, Circle.class))
117+
.isEqualTo(Circle.of(Point.of(1.2, 123.1), 10));
118+
119+
assertThat(codec.decode(encode(TEST, "((1.2,123.1),10)"), dataType, FORMAT_TEXT, Circle.class))
120+
.isEqualTo(Circle.of(Point.of(1.2, 123.1), 10));
121+
122+
assertThat(codec.decode(encode(TEST, "(1.2,123.1),10"), dataType, FORMAT_TEXT, Circle.class))
123+
.isEqualTo(Circle.of(Point.of(1.2, 123.1), 10));
124+
125+
assertThat(codec.decode(encode(TEST, "1.2,123.1,10"), dataType, FORMAT_TEXT, Circle.class))
126+
.isEqualTo(Circle.of(Point.of(1.2, 123.1), 10));
127+
}
128+
103129
@Test
104130
void encodeNull() {
105131
ParameterAssert.assertThat(new CircleCodec(TEST).encodeNull())
106-
.isEqualTo(new EncodedParameter(FORMAT_BINARY, CIRCLE.getObjectId(), NULL_VALUE));
132+
.isEqualTo(new EncodedParameter(FORMAT_BINARY, dataType, NULL_VALUE));
107133
}
108134

109135
}

src/test/java/io/r2dbc/postgresql/codec/DefaultCodecsUnitTests.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import static io.r2dbc.postgresql.client.EncodedParameter.NULL_VALUE;
2929
import static io.r2dbc.postgresql.client.ParameterAssert.assertThat;
3030
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.BOOL_ARRAY;
31+
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.CIRCLE_ARRAY;
3132
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT4_ARRAY;
3233
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT8_ARRAY;
3334
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT2;
@@ -108,6 +109,8 @@ void delegatePriority() {
108109
assertThat(codecs.decode(ByteBufUtils.encode(TEST, "{100,200}"), INT4_ARRAY.getObjectId(), FORMAT_TEXT, Object.class)).isEqualTo(new Integer[]{100, 200});
109110
assertThat(codecs.decode(ByteBufUtils.encode(TEST, "{100,200}"), INT8_ARRAY.getObjectId(), FORMAT_TEXT, Object.class)).isEqualTo(new Long[]{100L, 200L});
110111
assertThat(codecs.decode(ByteBufUtils.encode(TEST, "{alpha,bravo}"), VARCHAR_ARRAY.getObjectId(), FORMAT_TEXT, Object.class)).isEqualTo(new String[]{"alpha", "bravo"});
112+
assertThat(codecs.decode(ByteBufUtils.encode(TEST, "{\"((1.2, 123.1), 10)\",NULL}"), CIRCLE_ARRAY.getObjectId(), FORMAT_TEXT, Object.class))
113+
.isEqualTo(new Circle[]{Circle.of(Point.of(1.2, 123.1), 10), null});
111114
}
112115

113116
@Test

0 commit comments

Comments
 (0)