Skip to content

Commit c868d03

Browse files
committed
Polishing.
Refactor enum to string codecs into StringDecoder and StringArrayDecoder as fallback codecs if no other codec could be found. Allow reuse of ArrayCodec. [#429][resolves #454] Signed-off-by: Mark Paluch <[email protected]>
1 parent e4c774a commit c868d03

File tree

9 files changed

+293
-183
lines changed

9 files changed

+293
-183
lines changed

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

+29-25
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@
4343
*/
4444
class ArrayCodec<T> extends AbstractCodec<Object[]> {
4545

46-
private static final byte CLOSE_CURLY = '}';
46+
static final byte CLOSE_CURLY = '}';
4747

48-
private static final byte COMMA = ',';
48+
static final byte COMMA = ',';
4949

50-
private static final String NULL = "NULL";
50+
static final String NULL = "NULL";
5151

52-
private static final byte OPEN_CURLY = '{';
52+
static final byte OPEN_CURLY = '{';
5353

5454
private final ArrayCodecDelegate<T> delegate;
5555

@@ -159,9 +159,9 @@ Object[] doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format
159159
Assert.requireNonNull(type, "type must not be null");
160160

161161
if (FORMAT_BINARY == format) {
162-
return decodeBinary(buffer, this.dataType, type);
162+
return decodeBinary(buffer, this.dataType, this.delegate, this.componentType, type);
163163
} else {
164-
return decodeText(buffer, this.dataType, type);
164+
return decodeText(buffer, this.dataType, this.delimiter, this.delegate, this.componentType, type);
165165
}
166166
}
167167

@@ -257,14 +257,15 @@ private static int getDimensions(List<?> list) {
257257
return dims;
258258
}
259259

260-
Object[] decodeBinary(ByteBuf buffer, PostgresTypeIdentifier dataType, Class<?> returnType) {
260+
@SuppressWarnings("unchecked")
261+
static <T> T[] decodeBinary(ByteBuf buffer, PostgresTypeIdentifier dataType, Decoder<T> decoder, Class<T> componentType, Class<?> returnType) {
261262
if (!buffer.isReadable()) {
262-
return new Object[0];
263+
return (T[]) Array.newInstance(componentType, 0);
263264
}
264265

265266
int dimensions = buffer.readInt();
266267
if (dimensions == 0) {
267-
return (Object[]) Array.newInstance(this.componentType, 0);
268+
return (T[]) Array.newInstance(componentType, 0);
268269
}
269270

270271
if (returnType != Object.class) {
@@ -280,18 +281,19 @@ Object[] decodeBinary(ByteBuf buffer, PostgresTypeIdentifier dataType, Class<?>
280281
buffer.skipBytes(4); // lower bound ignored
281282
}
282283

283-
Object[] array = (Object[]) Array.newInstance(this.componentType, dims);
284+
T[] array = (T[]) Array.newInstance(componentType, dims);
284285

285-
readArrayAsBinary(buffer, dataType, array, dims, 0);
286+
readArrayAsBinary(buffer, dataType, array, dims, decoder, componentType, 0);
286287

287288
return array;
288289
}
289290

290-
Object[] decodeText(ByteBuf buffer, PostgresTypeIdentifier dataType, Class<?> returnType) {
291-
List<?> elements = decodeText(buffer, dataType);
291+
@SuppressWarnings("unchecked")
292+
static <T> T[] decodeText(ByteBuf buffer, PostgresTypeIdentifier dataType, byte delimiter, Decoder<T> decoder, Class<T> componentType, Class<?> returnType) {
293+
List<T> elements = (List<T>) decodeText(buffer, delimiter, dataType, decoder, componentType);
292294

293295
if (elements.isEmpty()) {
294-
return (Object[]) Array.newInstance(this.componentType, 0);
296+
return (T[]) Array.newInstance(componentType, 0);
295297
}
296298

297299
int dimensions = getDimensions(elements);
@@ -300,27 +302,29 @@ Object[] decodeText(ByteBuf buffer, PostgresTypeIdentifier dataType, Class<?> re
300302
Assert.requireArrayDimension(returnType, dimensions, "Dimensions mismatch: %s expected, but %s returned from DB");
301303
}
302304

303-
return toArray(elements, createArrayType(dimensions).getComponentType());
305+
return toArray(elements, (Class<T>) createArrayType(componentType, dimensions).getComponentType());
304306
}
305307

306-
private Class<?> createArrayType(int dims) {
308+
@SuppressWarnings("unchecked")
309+
private static <T> Class<T> createArrayType(Class<T> componentType, int dims) {
307310
int[] size = new int[dims];
308311
Arrays.fill(size, 1);
309-
return Array.newInstance(this.componentType, size).getClass();
312+
return (Class<T>) Array.newInstance(componentType, size).getClass();
310313
}
311314

312-
private static Object[] toArray(List<?> list, Class<?> returnType) {
315+
@SuppressWarnings("unchecked")
316+
private static <T> T[] toArray(List<T> list, Class<T> returnType) {
313317
List<Object> result = new ArrayList<>(list.size());
314318

315319
for (Object e : list) {
316-
Object o = (e instanceof List ? toArray((List<?>) e, returnType.getComponentType()) : e);
320+
Object o = (e instanceof List ? toArray((List<Object>) e, (Class<Object>) returnType.getComponentType()) : e);
317321
result.add(o);
318322
}
319323

320-
return result.toArray((Object[]) Array.newInstance(returnType, list.size()));
324+
return result.toArray((T[]) Array.newInstance(returnType, list.size()));
321325
}
322326

323-
private List<Object> decodeText(ByteBuf buf, PostgresTypeIdentifier dataType) {
327+
private static <T> List<Object> decodeText(ByteBuf buf, byte delimiter, PostgresTypeIdentifier dataType, Decoder<T> decoder, Class<T> componentType) {
324328
List<Object> arrayList = new ArrayList<>();
325329

326330
boolean insideString = false;
@@ -416,7 +420,7 @@ private List<Object> decodeText(ByteBuf buf, PostgresTypeIdentifier dataType) {
416420
if (!wasInsideString && slice.readableBytes() == 4 && slice.getByte(0) == 'N' && "NULL".equals(slice.toString(StandardCharsets.US_ASCII))) {
417421
currentArray.add(null);
418422
} else {
419-
currentArray.add(this.delegate.decode(slice, dataType, FORMAT_TEXT, this.componentType));
423+
currentArray.add(decoder.decode(slice, dataType, FORMAT_TEXT, componentType));
420424
}
421425
}
422426
} finally {
@@ -463,18 +467,18 @@ private void encodeAsText(ByteBuf byteBuf, Object[] value, Function<T, String> e
463467
byteBuf.writeByte(CLOSE_CURLY);
464468
}
465469

466-
private void readArrayAsBinary(ByteBuf buffer, PostgresTypeIdentifier dataType, Object[] array, int[] dims, int thisDimension) {
470+
private static <T> void readArrayAsBinary(ByteBuf buffer, PostgresTypeIdentifier dataType, Object[] array, int[] dims, Decoder<T> decoder, Class<T> componentType, int thisDimension) {
467471
if (thisDimension == dims.length - 1) {
468472
for (int i = 0; i < dims[thisDimension]; ++i) {
469473
int len = buffer.readInt();
470474
if (len == -1) {
471475
continue;
472476
}
473-
array[i] = this.delegate.decode(buffer.readSlice(len), dataType, FORMAT_BINARY, this.componentType);
477+
array[i] = decoder.decode(buffer.readSlice(len), dataType, FORMAT_BINARY, componentType);
474478
}
475479
} else {
476480
for (int i = 0; i < dims[thisDimension]; ++i) {
477-
readArrayAsBinary(buffer, dataType, (Object[]) array[i], dims, thisDimension + 1);
481+
readArrayAsBinary(buffer, dataType, (Object[]) array[i], dims, decoder, componentType, thisDimension + 1);
478482
}
479483
}
480484
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*
2626
* @since 0.8.8
2727
*/
28-
interface ArrayCodecDelegate<T> extends CodecMetadata {
28+
interface ArrayCodecDelegate<T> extends CodecMetadata, Decoder<T> {
2929

3030
/**
3131
* Encode the {@code value} to be used as parameter to be used as array element.
@@ -52,6 +52,7 @@ interface ArrayCodecDelegate<T> extends CodecMetadata {
5252
* @return the decoded value. Can be {@code null} if the value is {@code null}.
5353
*/
5454
@Nullable
55+
@Override
5556
T decode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, Class<? extends T> type);
5657

5758
}

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,16 @@ final class ConvertingArrayCodec<T> extends ArrayCodec<T> {
4949

5050
static final Set<PostgresqlObjectId> DATE_ARRAY_TYPES = EnumSet.of(DATE_ARRAY, TIMESTAMP_ARRAY, TIMESTAMPTZ_ARRAY, TIME_ARRAY, TIMETZ_ARRAY);
5151

52+
private final ArrayCodecDelegate<T> delegate;
53+
54+
private final Class<T> componentType;
55+
5256
private final Set<PostgresqlObjectId> supportedTypes;
5357

5458
public ConvertingArrayCodec(ByteBufAllocator byteBufAllocator, ArrayCodecDelegate<T> delegate, Class<T> componentType, Set<PostgresqlObjectId> supportedTypes) {
5559
super(byteBufAllocator, delegate, componentType);
60+
this.delegate = delegate;
61+
this.componentType = componentType;
5662
this.supportedTypes = supportedTypes;
5763
}
5864

@@ -75,9 +81,9 @@ Object[] doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format
7581
Assert.requireNonNull(type, "type must not be null");
7682

7783
if (FORMAT_BINARY == format) {
78-
return decodeBinary(buffer, dataType, type);
84+
return decodeBinary(buffer, dataType, this.delegate, this.componentType, type);
7985
} else {
80-
return decodeText(buffer, dataType, type);
86+
return decodeText(buffer, dataType, ArrayCodec.COMMA, this.delegate, this.componentType, type);
8187
}
8288
}
8389

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.r2dbc.postgresql.codec;
18+
19+
import io.netty.buffer.ByteBuf;
20+
import io.r2dbc.postgresql.message.Format;
21+
import reactor.util.annotation.Nullable;
22+
23+
/**
24+
* Decoder for a specific {@link PostgresTypeIdentifier} and {@link Class type}.
25+
*
26+
* @param <T> the type that is handled by this decoder.
27+
* @since 0.9
28+
*/
29+
interface Decoder<T> {
30+
31+
/**
32+
* Decode the {@link ByteBuf buffer} and return it as the requested {@link Class type}.
33+
*
34+
* @param buffer the data buffer
35+
* @param dataType the Postgres OID to encode
36+
* @param format the data type {@link Format}, text or binary
37+
* @param type the desired value type
38+
* @return the decoded value. Can be {@code null} if the value is {@code null}.
39+
*/
40+
@Nullable
41+
T decode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, Class<? extends T> type);
42+
43+
}

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

+15-4
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ public void addLast(Codec<?> codec) {
189189

190190
@Override
191191
@Nullable
192+
@SuppressWarnings("unchecked")
192193
public <T> T decode(@Nullable ByteBuf buffer, int dataType, Format format, Class<? extends T> type) {
193194
Assert.requireNonNull(format, "format must not be null");
194195
Assert.requireNonNull(type, "type must not be null");
@@ -200,14 +201,24 @@ public <T> T decode(@Nullable ByteBuf buffer, int dataType, Format format, Class
200201
Codec<T> codec = this.codecLookup.findDecodeCodec(dataType, format, type);
201202
if (codec != null) {
202203
return codec.decode(buffer, dataType, format, type);
203-
} else if (String.class == type) {
204+
}
205+
206+
if (String.class == type) {
204207
int varcharType = PostgresqlObjectId.VARCHAR.getObjectId();
205-
codec = this.codecLookup.findDecodeCodec(varcharType, format, type);
206-
if (codec != null) {
207-
return codec.decode(buffer, varcharType, format, type);
208+
Codec<T> varcharFallback = this.codecLookup.findDecodeCodec(varcharType, format, type);
209+
if (varcharFallback != null) {
210+
return varcharFallback.decode(buffer, varcharType, format, type);
208211
}
209212
}
210213

214+
if (StringCodec.STRING_DECODER.canDecode(dataType, format, type)) {
215+
return type.cast(StringCodec.STRING_DECODER.decode(buffer, dataType, format, (Class<String>) type));
216+
}
217+
218+
if (StringCodec.STRING_ARRAY_DECODER.canDecode(dataType, format, type)) {
219+
return type.cast(StringCodec.STRING_ARRAY_DECODER.decode(buffer, dataType, format, (Class<String[]>) type));
220+
}
221+
211222
throw new IllegalArgumentException(String.format("Cannot decode value of type %s with OID %d", type.getName(), dataType));
212223
}
213224

0 commit comments

Comments
 (0)