Skip to content

Commit

Permalink
Introduce fast-path handling for decoding primitive values.
Browse files Browse the repository at this point in the history
[resovles #662]
  • Loading branch information
mp911de committed Oct 2, 2024
1 parent a8830e7 commit f91c263
Show file tree
Hide file tree
Showing 15 changed files with 294 additions and 30 deletions.
23 changes: 19 additions & 4 deletions src/main/java/io/r2dbc/postgresql/PostgresqlRow.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public <T> T get(int index, Class<T> type) {
Assert.requireNonNull(type, "type must not be null");
requireNotReleased();

return decode(getColumn(index), type);
return decode(getColumn(index), null, type);
}

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

return decode(getColumn(name), type);
return decode(getColumn(name), name, type);
}

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

@Nullable
private <T> T decode(int index, Class<T> type) {
@SuppressWarnings("unchecked")
private <T> T decode(int index, @Nullable String name, Class<T> type) {
ByteBuf data = this.data[index];
if (data == null) {
if (type.isPrimitive()) {

String message;
if (name != null) {
message = String.format("Value at column '%s' is null. Cannot return value for primitive '%s'", name,
type.getName());
} else {
message = String.format("Value at column index %d is null. Cannot return value for primitive '%s'", index,
type.getName());
}

throw new NullPointerException(message);
}
return null;
}

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

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

return type.cast(postProcessResult(decoded));
Object result = postProcessResult(decoded);
return type.isPrimitive() ? (T) result : type.cast(result);

} finally {
data.readerIndex(readerIndex);
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/io/r2dbc/postgresql/codec/BooleanCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.BOOL;
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.BOOL_ARRAY;

final class BooleanCodec extends BuiltinCodecSupport<Boolean> {
final class BooleanCodec extends BuiltinCodecSupport<Boolean> implements PrimitiveWrapperCodecProvider<Boolean> {

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

@Override
public PrimitiveCodec<Boolean> getPrimitiveCodec() {
return new PrimitiveCodec<>(Boolean.TYPE, Boolean.class, this);
}

@Override
Boolean doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, @Nullable Format format, @Nullable Class<? extends Boolean> type) {
Assert.requireNonNull(buffer, "byteBuf must not be null");
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/io/r2dbc/postgresql/codec/ByteCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import io.r2dbc.postgresql.util.Assert;
import reactor.util.annotation.Nullable;

final class ByteCodec extends AbstractCodec<Byte> implements ArrayCodecDelegate<Byte> {
final class ByteCodec extends AbstractCodec<Byte> implements ArrayCodecDelegate<Byte>, PrimitiveWrapperCodecProvider<Byte> {

private final ShortCodec delegate;

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

@Override
public PrimitiveCodec<Byte> getPrimitiveCodec() {
return new PrimitiveCodec<>(Byte.TYPE, Byte.class, this);
}

@Override
public EncodedParameter encodeNull() {
return this.delegate.encodeNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,22 @@
import io.r2dbc.postgresql.util.Assert;
import reactor.util.annotation.Nullable;

final class CharacterCodec extends AbstractCodec<Character> implements ArrayCodecDelegate<Character> {
final class CharacterCodecProvider extends AbstractCodec<Character> implements ArrayCodecDelegate<Character>, PrimitiveWrapperCodecProvider<Character> {

private final StringCodec delegate;

CharacterCodec(ByteBufAllocator byteBufAllocator) {
CharacterCodecProvider(ByteBufAllocator byteBufAllocator) {
super(Character.class);

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

@Override
public PrimitiveCodec<Character> getPrimitiveCodec() {
return new PrimitiveCodec<>(Character.TYPE, Character.class, this);
}

@Override
public EncodedParameter encodeNull() {
return this.delegate.encodeNull();
Expand Down
18 changes: 11 additions & 7 deletions src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private static List<Codec<?>> getDefaultCodecs(ByteBufAllocator byteBufAllocator
new BigDecimalCodec(byteBufAllocator),
new BigIntegerCodec(byteBufAllocator),
new BooleanCodec(byteBufAllocator),
new CharacterCodec(byteBufAllocator),
new CharacterCodecProvider(byteBufAllocator),
new DoubleCodec(byteBufAllocator),
new FloatCodec(byteBufAllocator),
new InetAddressCodec(byteBufAllocator),
Expand Down Expand Up @@ -159,7 +159,7 @@ private static List<Codec<?>> getDefaultCodecs(ByteBufAllocator byteBufAllocator
new PolygonCodec(byteBufAllocator)
));

List<Codec<?>> defaultArrayCodecs = new ArrayList<>();
List<Codec<?>> additionalCodecs = new ArrayList<>();

for (Codec<?> codec : codecs) {

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

if (codec instanceof BoxCodec) {
// BOX[] uses a ';' as a delimiter (i.e. "{(3.7,4.6),(1.9,2.8);(5,7),(1.5,3.3)}")
defaultArrayCodecs.add(new ArrayCodec(byteBufAllocator, delegate.getArrayDataType(), delegate, componentType, (byte) ';'));
additionalCodecs.add(new ArrayCodec(byteBufAllocator, delegate.getArrayDataType(), delegate, componentType, (byte) ';'));
} else if (codec instanceof AbstractNumericCodec) {
defaultArrayCodecs.add(new ConvertingArrayCodec(byteBufAllocator, delegate, componentType, ConvertingArrayCodec.NUMERIC_ARRAY_TYPES));
additionalCodecs.add(new ConvertingArrayCodec(byteBufAllocator, delegate, componentType, ConvertingArrayCodec.NUMERIC_ARRAY_TYPES));
} else if (codec instanceof AbstractTemporalCodec) {
defaultArrayCodecs.add(new ConvertingArrayCodec(byteBufAllocator, delegate, componentType, ConvertingArrayCodec.DATE_ARRAY_TYPES));
additionalCodecs.add(new ConvertingArrayCodec(byteBufAllocator, delegate, componentType, ConvertingArrayCodec.DATE_ARRAY_TYPES));
} else {
defaultArrayCodecs.add(new ArrayCodec(byteBufAllocator, delegate, componentType));
additionalCodecs.add(new ArrayCodec(byteBufAllocator, delegate, componentType));
}
}

if (codec instanceof PrimitiveWrapperCodecProvider<?>) {
additionalCodecs.add(((PrimitiveWrapperCodecProvider<?>) codec).getPrimitiveCodec());
}
}

codecs.addAll(defaultArrayCodecs);
codecs.addAll(additionalCodecs);

return codecs;
}
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/io/r2dbc/postgresql/codec/DoubleCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT8;
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT8_ARRAY;

final class DoubleCodec extends AbstractNumericCodec<Double> {
final class DoubleCodec extends AbstractNumericCodec<Double> implements PrimitiveWrapperCodecProvider<Double> {

DoubleCodec(ByteBufAllocator byteBufAllocator) {
super(Double.class, byteBufAllocator);
}

@Override
public PrimitiveCodec<Double> getPrimitiveCodec() {
return new PrimitiveCodec<>(Double.TYPE, Double.class, this);
}

@Override
Double doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, @Nullable Class<? extends Double> type) {
Assert.requireNonNull(buffer, "byteBuf must not be null");
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/io/r2dbc/postgresql/codec/FloatCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT4;
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.FLOAT4_ARRAY;

final class FloatCodec extends AbstractNumericCodec<Float> {
final class FloatCodec extends AbstractNumericCodec<Float> implements PrimitiveWrapperCodecProvider<Float> {

FloatCodec(ByteBufAllocator byteBufAllocator) {
super(Float.class, byteBufAllocator);
}

@Override
public PrimitiveCodec<Float> getPrimitiveCodec() {
return new PrimitiveCodec<>(Float.TYPE, Float.class, this);
}

@Override
Float doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, @Nullable Class<? extends Float> type) {
Assert.requireNonNull(buffer, "byteBuf must not be null");
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/io/r2dbc/postgresql/codec/IntegerCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT4;
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT4_ARRAY;

final class IntegerCodec extends AbstractNumericCodec<Integer> {
final class IntegerCodec extends AbstractNumericCodec<Integer> implements PrimitiveWrapperCodecProvider<Integer> {

IntegerCodec(ByteBufAllocator byteBufAllocator) {
super(Integer.class, byteBufAllocator);
}

@Override
public PrimitiveCodec<Integer> getPrimitiveCodec() {
return new PrimitiveCodec<>(Integer.TYPE, Integer.class, this);
}

@Override
Integer doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, @Nullable Format format, @Nullable Class<? extends Integer> type) {
Assert.requireNonNull(buffer, "byteBuf must not be null");
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/io/r2dbc/postgresql/codec/LongCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT8;
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT8_ARRAY;

final class LongCodec extends AbstractNumericCodec<Long> {
final class LongCodec extends AbstractNumericCodec<Long> implements PrimitiveWrapperCodecProvider<Long> {

LongCodec(ByteBufAllocator byteBufAllocator) {
super(Long.class, byteBufAllocator);
}

@Override
public PrimitiveCodec<Long> getPrimitiveCodec() {
return new PrimitiveCodec<>(Long.TYPE, Long.class, this);
}

@Override
Long doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, @Nullable Class<? extends Long> type) {
Assert.requireNonNull(buffer, "byteBuf must not be null");
Expand Down
85 changes: 85 additions & 0 deletions src/main/java/io/r2dbc/postgresql/codec/PrimitiveCodec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2024 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 io.r2dbc.postgresql.codec;

import io.netty.buffer.ByteBuf;
import io.r2dbc.postgresql.client.EncodedParameter;
import io.r2dbc.postgresql.message.Format;
import io.r2dbc.postgresql.util.Assert;

/**
* @since 1.0.6
*/
final class PrimitiveCodec<T> implements Codec<Object> {

private final Class<?> primitiveType;

private final Class<T> wrapperType;

private final Codec<T> boxedCodec;

public PrimitiveCodec(Class<?> primitiveType, Class<T> wrapperType, Codec<T> boxedCodec) {

this.primitiveType = Assert.requireNonNull(primitiveType, "primitiveType must not be null");
this.wrapperType = Assert.requireNonNull(wrapperType, "wrapperType must not be null");
this.boxedCodec = Assert.requireNonNull(boxedCodec, "boxedCodec must not be null");

Assert.isTrue(primitiveType.isPrimitive(), "primitiveType must be a primitive type");
}

@Override
public boolean canDecode(int dataType, Format format, Class<?> type) {
return this.primitiveType.equals(type) && this.boxedCodec.canDecode(dataType, format, this.wrapperType);
}

@Override
public boolean canEncode(Object value) {
return this.boxedCodec.canEncode(value);
}

@Override
public boolean canEncodeNull(Class<?> type) {
return this.primitiveType.equals(type) && this.boxedCodec.canEncodeNull(this.wrapperType);
}

@Override
public Object decode(ByteBuf buffer, int dataType, Format format, Class<?> type) {

T value = this.boxedCodec.decode(buffer, dataType, format, this.wrapperType);

if (value == null) {
throw new NullPointerException("value for primitive type " + this.primitiveType.getName() + " is null");
}

return value;
}

@Override
public EncodedParameter encode(Object value) {
return this.boxedCodec.encode(value);
}

@Override
public EncodedParameter encode(Object value, int dataType) {
return this.boxedCodec.encode(value, dataType);
}

@Override
public EncodedParameter encodeNull() {
return this.boxedCodec.encodeNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2024 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 io.r2dbc.postgresql.codec;

/**
* Codec for a primitive wrapper such as {@link DoubleCodec} for {@link Double}. {@link #getPrimitiveCodec()} is expected to return a {@code double} codec.
*
* @since 1.0.6
*/
interface PrimitiveWrapperCodecProvider<T> {

/**
* Return the codec for its primitive type.
*
* @return the codec for its primitive type
*/
PrimitiveCodec<T> getPrimitiveCodec();
}
7 changes: 6 additions & 1 deletion src/main/java/io/r2dbc/postgresql/codec/ShortCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT2;
import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT2_ARRAY;

final class ShortCodec extends AbstractNumericCodec<Short> {
final class ShortCodec extends AbstractNumericCodec<Short> implements PrimitiveWrapperCodecProvider<Short> {

ShortCodec(ByteBufAllocator byteBufAllocator) {
super(Short.class, byteBufAllocator);
}

@Override
public PrimitiveCodec<Short> getPrimitiveCodec() {
return new PrimitiveCodec<>(Short.TYPE, Short.class, this);
}

@Override
Short doDecode(ByteBuf buffer, PostgresTypeIdentifier dataType, Format format, @Nullable Class<? extends Short> type) {
Assert.requireNonNull(buffer, "byteBuf must not be null");
Expand Down
Loading

0 comments on commit f91c263

Please sign in to comment.