Skip to content

Commit 11ccd0a

Browse files
committed
Introduce fast-path handling for decoding primitive values.
[resovles #662]
1 parent de1b90c commit 11ccd0a

15 files changed

+294
-30
lines changed

src/main/java/io/r2dbc/postgresql/PostgresqlRow.java

+19-4
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public <T> T get(int index, Class<T> type) {
8383
Assert.requireNonNull(type, "type must not be null");
8484
requireNotReleased();
8585

86-
return decode(getColumn(index), type);
86+
return decode(getColumn(index), null, type);
8787
}
8888

8989
@Nullable
@@ -93,7 +93,7 @@ public <T> T get(String name, Class<T> type) {
9393
Assert.requireNonNull(type, "type must not be null");
9494
requireNotReleased();
9595

96-
return decode(getColumn(name), type);
96+
return decode(getColumn(name), name, type);
9797
}
9898

9999
@Override
@@ -102,9 +102,23 @@ public io.r2dbc.postgresql.api.PostgresqlRowMetadata getMetadata() {
102102
}
103103

104104
@Nullable
105-
private <T> T decode(int index, Class<T> type) {
105+
@SuppressWarnings("unchecked")
106+
private <T> T decode(int index, @Nullable String name, Class<T> type) {
106107
ByteBuf data = this.data[index];
107108
if (data == null) {
109+
if (type.isPrimitive()) {
110+
111+
String message;
112+
if (name != null) {
113+
message = String.format("Value at column '%s' is null. Cannot return value for primitive '%s'", name,
114+
type.getName());
115+
} else {
116+
message = String.format("Value at column index %d is null. Cannot return value for primitive '%s'", index,
117+
type.getName());
118+
}
119+
120+
throw new NullPointerException(message);
121+
}
108122
return null;
109123
}
110124

@@ -114,7 +128,8 @@ private <T> T decode(int index, Class<T> type) {
114128

115129
T decoded = this.context.getCodecs().decode(data, field.getDataType(), field.getFormat(), type);
116130

117-
return type.cast(postProcessResult(decoded));
131+
Object result = postProcessResult(decoded);
132+
return type.isPrimitive() ? (T) result : type.cast(result);
118133

119134
} finally {
120135
data.readerIndex(readerIndex);

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@
2626
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.BOOL;
2727
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.BOOL_ARRAY;
2828

29-
final class BooleanCodec extends BuiltinCodecSupport<Boolean> {
29+
final class BooleanCodec extends BuiltinCodecSupport<Boolean> implements PrimitiveWrapperCodecProvider<Boolean> {
3030

3131
BooleanCodec(ByteBufAllocator byteBufAllocator) {
3232
super(Boolean.class, byteBufAllocator, BOOL, BOOL_ARRAY, it -> it ? "t" : "f");
3333
}
3434

35+
@Override
36+
public PrimitiveCodec<Boolean> getPrimitiveCodec() {
37+
return new PrimitiveCodec<>(Boolean.TYPE, Boolean.class, this);
38+
}
39+
3540
@Override
3641
Boolean doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, @Nullable Format format, @Nullable Class<? extends Boolean> type) {
3742
Assert.requireNonNull(buffer, "byteBuf must not be null");

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import io.r2dbc.postgresql.util.Assert;
2424
import reactor.util.annotation.Nullable;
2525

26-
final class ByteCodec extends AbstractCodec<Byte> implements ArrayCodecDelegate<Byte> {
26+
final class ByteCodec extends AbstractCodec<Byte> implements ArrayCodecDelegate<Byte>, PrimitiveWrapperCodecProvider<Byte> {
2727

2828
private final ShortCodec delegate;
2929

@@ -34,6 +34,11 @@ final class ByteCodec extends AbstractCodec<Byte> implements ArrayCodecDelegate<
3434
this.delegate = new ShortCodec(byteBufAllocator);
3535
}
3636

37+
@Override
38+
public PrimitiveCodec<Byte> getPrimitiveCodec() {
39+
return new PrimitiveCodec<>(Byte.TYPE, Byte.class, this);
40+
}
41+
3742
@Override
3843
public EncodedParameter encodeNull() {
3944
return this.delegate.encodeNull();

src/main/java/io/r2dbc/postgresql/codec/CharacterCodec.java renamed to src/main/java/io/r2dbc/postgresql/codec/CharacterCodecProvider.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,22 @@
2323
import io.r2dbc.postgresql.util.Assert;
2424
import reactor.util.annotation.Nullable;
2525

26-
final class CharacterCodec extends AbstractCodec<Character> implements ArrayCodecDelegate<Character> {
26+
final class CharacterCodecProvider extends AbstractCodec<Character> implements ArrayCodecDelegate<Character>, PrimitiveWrapperCodecProvider<Character> {
2727

2828
private final StringCodec delegate;
2929

30-
CharacterCodec(ByteBufAllocator byteBufAllocator) {
30+
CharacterCodecProvider(ByteBufAllocator byteBufAllocator) {
3131
super(Character.class);
3232

3333
Assert.requireNonNull(byteBufAllocator, "byteBufAllocator must not be null");
3434
this.delegate = new StringCodec(byteBufAllocator);
3535
}
3636

37+
@Override
38+
public PrimitiveCodec<Character> getPrimitiveCodec() {
39+
return new PrimitiveCodec<>(Character.TYPE, Character.class, this);
40+
}
41+
3742
@Override
3843
public EncodedParameter encodeNull() {
3944
return this.delegate.encodeNull();

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

+11-7
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ private static List<Codec<?>> getDefaultCodecs(ByteBufAllocator byteBufAllocator
111111
new BigDecimalCodec(byteBufAllocator),
112112
new BigIntegerCodec(byteBufAllocator),
113113
new BooleanCodec(byteBufAllocator),
114-
new CharacterCodec(byteBufAllocator),
114+
new CharacterCodecProvider(byteBufAllocator),
115115
new DoubleCodec(byteBufAllocator),
116116
new FloatCodec(byteBufAllocator),
117117
new InetAddressCodec(byteBufAllocator),
@@ -159,7 +159,7 @@ private static List<Codec<?>> getDefaultCodecs(ByteBufAllocator byteBufAllocator
159159
new PolygonCodec(byteBufAllocator)
160160
));
161161

162-
List<Codec<?>> defaultArrayCodecs = new ArrayList<>();
162+
List<Codec<?>> additionalCodecs = new ArrayList<>();
163163

164164
for (Codec<?> codec : codecs) {
165165

@@ -171,18 +171,22 @@ private static List<Codec<?>> getDefaultCodecs(ByteBufAllocator byteBufAllocator
171171

172172
if (codec instanceof BoxCodec) {
173173
// BOX[] uses a ';' as a delimiter (i.e. "{(3.7,4.6),(1.9,2.8);(5,7),(1.5,3.3)}")
174-
defaultArrayCodecs.add(new ArrayCodec(byteBufAllocator, delegate.getArrayDataType(), delegate, componentType, (byte) ';'));
174+
additionalCodecs.add(new ArrayCodec(byteBufAllocator, delegate.getArrayDataType(), delegate, componentType, (byte) ';'));
175175
} else if (codec instanceof AbstractNumericCodec) {
176-
defaultArrayCodecs.add(new ConvertingArrayCodec(byteBufAllocator, delegate, componentType, ConvertingArrayCodec.NUMERIC_ARRAY_TYPES));
176+
additionalCodecs.add(new ConvertingArrayCodec(byteBufAllocator, delegate, componentType, ConvertingArrayCodec.NUMERIC_ARRAY_TYPES));
177177
} else if (codec instanceof AbstractTemporalCodec) {
178-
defaultArrayCodecs.add(new ConvertingArrayCodec(byteBufAllocator, delegate, componentType, ConvertingArrayCodec.DATE_ARRAY_TYPES));
178+
additionalCodecs.add(new ConvertingArrayCodec(byteBufAllocator, delegate, componentType, ConvertingArrayCodec.DATE_ARRAY_TYPES));
179179
} else {
180-
defaultArrayCodecs.add(new ArrayCodec(byteBufAllocator, delegate, componentType));
180+
additionalCodecs.add(new ArrayCodec(byteBufAllocator, delegate, componentType));
181181
}
182182
}
183+
184+
if (codec instanceof PrimitiveWrapperCodecProvider<?>) {
185+
additionalCodecs.add(((PrimitiveWrapperCodecProvider<?>) codec).getPrimitiveCodec());
186+
}
183187
}
184188

185-
codecs.addAll(defaultArrayCodecs);
189+
codecs.addAll(additionalCodecs);
186190

187191
return codecs;
188192
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@
2525
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT8;
2626
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT8_ARRAY;
2727

28-
final class DoubleCodec extends AbstractNumericCodec<Double> {
28+
final class DoubleCodec extends AbstractNumericCodec<Double> implements PrimitiveWrapperCodecProvider<Double> {
2929

3030
DoubleCodec(ByteBufAllocator byteBufAllocator) {
3131
super(Double.class, byteBufAllocator);
3232
}
3333

34+
@Override
35+
public PrimitiveCodec<Double> getPrimitiveCodec() {
36+
return new PrimitiveCodec<>(Double.TYPE, Double.class, this);
37+
}
38+
3439
@Override
3540
Double doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, @Nullable Class<? extends Double> type) {
3641
Assert.requireNonNull(buffer, "byteBuf must not be null");

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@
2525
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT4;
2626
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT4_ARRAY;
2727

28-
final class FloatCodec extends AbstractNumericCodec<Float> {
28+
final class FloatCodec extends AbstractNumericCodec<Float> implements PrimitiveWrapperCodecProvider<Float> {
2929

3030
FloatCodec(ByteBufAllocator byteBufAllocator) {
3131
super(Float.class, byteBufAllocator);
3232
}
3333

34+
@Override
35+
public PrimitiveCodec<Float> getPrimitiveCodec() {
36+
return new PrimitiveCodec<>(Float.TYPE, Float.class, this);
37+
}
38+
3439
@Override
3540
Float doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, @Nullable Class<? extends Float> type) {
3641
Assert.requireNonNull(buffer, "byteBuf must not be null");

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@
2525
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT4;
2626
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT4_ARRAY;
2727

28-
final class IntegerCodec extends AbstractNumericCodec<Integer> {
28+
final class IntegerCodec extends AbstractNumericCodec<Integer> implements PrimitiveWrapperCodecProvider<Integer> {
2929

3030
IntegerCodec(ByteBufAllocator byteBufAllocator) {
3131
super(Integer.class, byteBufAllocator);
3232
}
3333

34+
@Override
35+
public PrimitiveCodec<Integer> getPrimitiveCodec() {
36+
return new PrimitiveCodec<>(Integer.TYPE, Integer.class, this);
37+
}
38+
3439
@Override
3540
Integer doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, @Nullable Format format, @Nullable Class<? extends Integer> type) {
3641
Assert.requireNonNull(buffer, "byteBuf must not be null");

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@
2525
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT8;
2626
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT8_ARRAY;
2727

28-
final class LongCodec extends AbstractNumericCodec<Long> {
28+
final class LongCodec extends AbstractNumericCodec<Long> implements PrimitiveWrapperCodecProvider<Long> {
2929

3030
LongCodec(ByteBufAllocator byteBufAllocator) {
3131
super(Long.class, byteBufAllocator);
3232
}
3333

34+
@Override
35+
public PrimitiveCodec<Long> getPrimitiveCodec() {
36+
return new PrimitiveCodec<>(Long.TYPE, Long.class, this);
37+
}
38+
3439
@Override
3540
Long doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, @Nullable Class<? extends Long> type) {
3641
Assert.requireNonNull(buffer, "byteBuf must not be null");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2024 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.client.EncodedParameter;
21+
import io.r2dbc.postgresql.message.Format;
22+
import io.r2dbc.postgresql.util.Assert;
23+
24+
/**
25+
* @since 1.0.6
26+
*/
27+
final class PrimitiveCodec<T> implements Codec<Object> {
28+
29+
private final Class<?> primitiveType;
30+
31+
private final Class<T> wrapperType;
32+
33+
private final Codec<T> boxedCodec;
34+
35+
public PrimitiveCodec(Class<?> primitiveType, Class<T> wrapperType, Codec<T> boxedCodec) {
36+
37+
this.primitiveType = Assert.requireNonNull(primitiveType, "primitiveType must not be null");
38+
this.wrapperType = Assert.requireNonNull(wrapperType, "wrapperType must not be null");
39+
this.boxedCodec = Assert.requireNonNull(boxedCodec, "boxedCodec must not be null");
40+
41+
Assert.isTrue(primitiveType.isPrimitive(), "primitiveType must be a primitive type");
42+
}
43+
44+
@Override
45+
public boolean canDecode(int dataType, Format format, Class<?> type) {
46+
return this.primitiveType.equals(type) && this.boxedCodec.canDecode(dataType, format, this.wrapperType);
47+
}
48+
49+
@Override
50+
public boolean canEncode(Object value) {
51+
return this.boxedCodec.canEncode(value);
52+
}
53+
54+
@Override
55+
public boolean canEncodeNull(Class<?> type) {
56+
return this.primitiveType.equals(type) && this.boxedCodec.canEncodeNull(this.wrapperType);
57+
}
58+
59+
@Override
60+
public Object decode(ByteBuf buffer, int dataType, Format format, Class<?> type) {
61+
62+
T value = this.boxedCodec.decode(buffer, dataType, format, this.wrapperType);
63+
64+
if (value == null) {
65+
throw new NullPointerException("value for primitive type " + this.primitiveType.getName() + " is null");
66+
}
67+
68+
return value;
69+
}
70+
71+
@Override
72+
public EncodedParameter encode(Object value) {
73+
return this.boxedCodec.encode(value);
74+
}
75+
76+
@Override
77+
public EncodedParameter encode(Object value, int dataType) {
78+
return this.boxedCodec.encode(value, dataType);
79+
}
80+
81+
@Override
82+
public EncodedParameter encodeNull() {
83+
return this.boxedCodec.encodeNull();
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2024 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+
/**
20+
* Codec for a primitive wrapper such as {@link DoubleCodec} for {@link Double}. {@link #getPrimitiveCodec()} is expected to return a {@code double} codec.
21+
*
22+
* @since 1.0.6
23+
*/
24+
interface PrimitiveWrapperCodecProvider<T> {
25+
26+
/**
27+
* Return the codec for its primitive type.
28+
*
29+
* @return the codec for its primitive type
30+
*/
31+
PrimitiveCodec<T> getPrimitiveCodec();
32+
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@
2525
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT2;
2626
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT2_ARRAY;
2727

28-
final class ShortCodec extends AbstractNumericCodec<Short> {
28+
final class ShortCodec extends AbstractNumericCodec<Short> implements PrimitiveWrapperCodecProvider<Short> {
2929

3030
ShortCodec(ByteBufAllocator byteBufAllocator) {
3131
super(Short.class, byteBufAllocator);
3232
}
3333

34+
@Override
35+
public PrimitiveCodec<Short> getPrimitiveCodec() {
36+
return new PrimitiveCodec<>(Short.TYPE, Short.class, this);
37+
}
38+
3439
@Override
3540
Short doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, @Nullable Class<? extends Short> type) {
3641
Assert.requireNonNull(buffer, "byteBuf must not be null");

0 commit comments

Comments
 (0)