diff --git a/src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java b/src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java index 5e5d9473..261fc223 100644 --- a/src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java +++ b/src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java @@ -44,10 +44,10 @@ */ public final class DefaultCodecs implements Codecs, CodecRegistry { - private final List> codecs; - private final CodecLookup codecLookup; + private final List> codecs; + /** * Create a new instance of {@link DefaultCodecs} preferring detached (copied buffers). * @@ -96,97 +96,6 @@ public DefaultCodecs(ByteBufAllocator byteBufAllocator, boolean preferAttachedBu this.codecLookup.afterCodecAdded(); } - @SuppressWarnings({"unchecked", "rawtypes"}) - private static List> getDefaultCodecs(ByteBufAllocator byteBufAllocator, boolean preferAttachedBuffers, CodecConfiguration configuration) { - - List> codecs = new CopyOnWriteArrayList<>(Arrays.asList( - - // Prioritized Codecs - new StringCodec(byteBufAllocator), - new InstantCodec(byteBufAllocator, configuration::getZoneId), - new ZonedDateTimeCodec(byteBufAllocator), - new BinaryByteBufferCodec(byteBufAllocator), - new BinaryByteArrayCodec(byteBufAllocator), - - new BigDecimalCodec(byteBufAllocator), - new BigIntegerCodec(byteBufAllocator), - new BooleanCodec(byteBufAllocator), - new CharacterCodec(byteBufAllocator), - new DoubleCodec(byteBufAllocator), - new FloatCodec(byteBufAllocator), - new InetAddressCodec(byteBufAllocator), - new IntegerCodec(byteBufAllocator), - new IntervalCodec(byteBufAllocator), - new LocalDateCodec(byteBufAllocator), - new LocalDateTimeCodec(byteBufAllocator, configuration::getZoneId), - new LocalTimeCodec(byteBufAllocator), - new LongCodec(byteBufAllocator), - new OffsetDateTimeCodec(byteBufAllocator), - new OffsetTimeCodec(byteBufAllocator), - new ShortCodec(byteBufAllocator), - new UriCodec(byteBufAllocator), - new UrlCodec(byteBufAllocator), - new UuidCodec(byteBufAllocator), - new ZoneIdCodec(byteBufAllocator), - - // JSON - new JsonCodec(byteBufAllocator, preferAttachedBuffers), - new JsonByteArrayCodec(byteBufAllocator), - new JsonByteBufCodec(byteBufAllocator), - new JsonByteBufferCodec(byteBufAllocator), - new JsonInputStreamCodec(byteBufAllocator), - new JsonStringCodec(byteBufAllocator), - - // Fallback for Object.class - new ByteCodec(byteBufAllocator), - new DateCodec(byteBufAllocator, configuration::getZoneId), - - new BlobCodec(byteBufAllocator), - new ClobCodec(byteBufAllocator), - RefCursorCodec.INSTANCE, - RefCursorNameCodec.INSTANCE, - - // Array - new StringArrayCodec(byteBufAllocator), - - // Geometry - new CircleCodec(byteBufAllocator), - new PointCodec(byteBufAllocator), - new BoxCodec(byteBufAllocator), - new LineCodec(byteBufAllocator), - new LsegCodec(byteBufAllocator), - new PathCodec(byteBufAllocator), - new PolygonCodec(byteBufAllocator) - )); - - List> defaultArrayCodecs = new ArrayList<>(); - - for (Codec codec : codecs) { - - if (codec instanceof ArrayCodecDelegate) { - - Assert.requireType(codec, AbstractCodec.class, "Codec " + codec + " must be a subclass of AbstractCodec to be registered as generic array codec"); - ArrayCodecDelegate delegate = (ArrayCodecDelegate) codec; - Class componentType = delegate.type(); - - 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) ';')); - } else if (codec instanceof AbstractNumericCodec) { - defaultArrayCodecs.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)); - } else { - defaultArrayCodecs.add(new ArrayCodec(byteBufAllocator, delegate, componentType)); - } - } - } - - codecs.addAll(defaultArrayCodecs); - - return codecs; - } - @Override public void addFirst(Codec codec) { Assert.requireNonNull(codec, "codec must not be null"); @@ -264,6 +173,35 @@ public EncodedParameter encode(Object value) { return encodeParameterValue(value, dataType, parameterValue); } + @Override + public EncodedParameter encodeNull(Class type) { + Assert.requireNonNull(type, "type must not be null"); + + Codec codec = this.codecLookup.findEncodeNullCodec(type); + if (codec != null) { + return codec.encodeNull(); + } + + throw new IllegalArgumentException(String.format("Cannot encode null parameter of type %s", type.getName())); + } + + @Override + public Iterator> iterator() { + return Collections.unmodifiableList(new ArrayList<>(this.codecs)).iterator(); + } + + @Override + public Class preferredType(int dataType, Format format) { + Assert.requireNonNull(format, "format must not be null"); + + Codec codec = this.codecLookup.findDecodeCodec(dataType, format, Object.class); + if (codec instanceof CodecMetadata) { + return ((CodecMetadata) codec).type(); + } + + return null; + } + EncodedParameter encodeParameterValue(Object value, @Nullable PostgresTypeIdentifier dataType, @Nullable Object parameterValue) { if (dataType == null) { @@ -290,33 +228,96 @@ EncodedParameter encodeParameterValue(Object value, @Nullable PostgresTypeIdenti throw new IllegalArgumentException(String.format("Cannot encode parameter of type %s (%s)", value.getClass().getName(), parameterValue)); } - @Override - public EncodedParameter encodeNull(Class type) { - Assert.requireNonNull(type, "type must not be null"); + @SuppressWarnings({"unchecked", "rawtypes"}) + private static List> getDefaultCodecs(ByteBufAllocator byteBufAllocator, boolean preferAttachedBuffers, CodecConfiguration configuration) { - Codec codec = this.codecLookup.findEncodeNullCodec(type); - if (codec != null) { - return codec.encodeNull(); - } + List> codecs = new CopyOnWriteArrayList<>(Arrays.asList( - throw new IllegalArgumentException(String.format("Cannot encode null parameter of type %s", type.getName())); - } + // Prioritized Codecs + new StringCodec(byteBufAllocator), + new InstantCodec(byteBufAllocator, configuration::getZoneId), + new ZonedDateTimeCodec(byteBufAllocator), + new BinaryByteBufferCodec(byteBufAllocator), + new BinaryByteArrayCodec(byteBufAllocator), - @Override - public Class preferredType(int dataType, Format format) { - Assert.requireNonNull(format, "format must not be null"); + new BigDecimalCodec(byteBufAllocator), + new BigIntegerCodec(byteBufAllocator), + new BooleanCodec(byteBufAllocator), + new CharacterCodec(byteBufAllocator), + new DoubleCodec(byteBufAllocator), + new FloatCodec(byteBufAllocator), + new InetAddressCodec(byteBufAllocator), + new IntegerCodec(byteBufAllocator), + new IntervalCodec(byteBufAllocator), + new LocalDateCodec(byteBufAllocator), + new LocalDateTimeCodec(byteBufAllocator, configuration::getZoneId), + new LocalTimeCodec(byteBufAllocator), + new LongCodec(byteBufAllocator), + new OffsetDateTimeCodec(byteBufAllocator), + new OffsetTimeCodec(byteBufAllocator), + new ShortCodec(byteBufAllocator), + new UriCodec(byteBufAllocator), + new UrlCodec(byteBufAllocator), + new UuidCodec(byteBufAllocator), + new ZoneIdCodec(byteBufAllocator), + new YearCodec(byteBufAllocator), - Codec codec = this.codecLookup.findDecodeCodec(dataType, format, Object.class); - if (codec instanceof CodecMetadata) { - return ((CodecMetadata) codec).type(); + // JSON + new JsonCodec(byteBufAllocator, preferAttachedBuffers), + new JsonByteArrayCodec(byteBufAllocator), + new JsonByteBufCodec(byteBufAllocator), + new JsonByteBufferCodec(byteBufAllocator), + new JsonInputStreamCodec(byteBufAllocator), + new JsonStringCodec(byteBufAllocator), + + // Fallback for Object.class + new ByteCodec(byteBufAllocator), + new DateCodec(byteBufAllocator, configuration::getZoneId), + + new BlobCodec(byteBufAllocator), + new ClobCodec(byteBufAllocator), + RefCursorCodec.INSTANCE, + RefCursorNameCodec.INSTANCE, + + // Array + new StringArrayCodec(byteBufAllocator), + + // Geometry + new CircleCodec(byteBufAllocator), + new PointCodec(byteBufAllocator), + new BoxCodec(byteBufAllocator), + new LineCodec(byteBufAllocator), + new LsegCodec(byteBufAllocator), + new PathCodec(byteBufAllocator), + new PolygonCodec(byteBufAllocator) + )); + + List> defaultArrayCodecs = new ArrayList<>(); + + for (Codec codec : codecs) { + + if (codec instanceof ArrayCodecDelegate) { + + Assert.requireType(codec, AbstractCodec.class, "Codec " + codec + " must be a subclass of AbstractCodec to be registered as generic array codec"); + ArrayCodecDelegate delegate = (ArrayCodecDelegate) codec; + Class componentType = delegate.type(); + + 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) ';')); + } else if (codec instanceof AbstractNumericCodec) { + defaultArrayCodecs.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)); + } else { + defaultArrayCodecs.add(new ArrayCodec(byteBufAllocator, delegate, componentType)); + } + } } - return null; - } + codecs.addAll(defaultArrayCodecs); - @Override - public Iterator> iterator() { - return Collections.unmodifiableList(new ArrayList<>(this.codecs)).iterator(); + return codecs; } } diff --git a/src/main/java/io/r2dbc/postgresql/codec/YearCodec.java b/src/main/java/io/r2dbc/postgresql/codec/YearCodec.java new file mode 100644 index 00000000..e0763eeb --- /dev/null +++ b/src/main/java/io/r2dbc/postgresql/codec/YearCodec.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017 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.ByteBufAllocator; + +import java.time.Year; + +final class YearCodec extends IntegerCodecDelegate { + + YearCodec(ByteBufAllocator byteBufAllocator) { + super(Year.class, byteBufAllocator, Year::getValue, Year::of); + } +} diff --git a/src/test/java/io/r2dbc/postgresql/codec/YearCodecTest.java b/src/test/java/io/r2dbc/postgresql/codec/YearCodecTest.java new file mode 100644 index 00000000..96d78e59 --- /dev/null +++ b/src/test/java/io/r2dbc/postgresql/codec/YearCodecTest.java @@ -0,0 +1,112 @@ +package io.r2dbc.postgresql.codec; + +import io.r2dbc.postgresql.client.EncodedParameter; +import io.r2dbc.postgresql.client.ParameterAssert; +import org.junit.jupiter.api.Test; + +import java.time.Year; + +import static io.r2dbc.postgresql.client.EncodedParameter.NULL_VALUE; +import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT2; +import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT4; +import static io.r2dbc.postgresql.codec.PostgresqlObjectId.INT8; +import static io.r2dbc.postgresql.codec.PostgresqlObjectId.NUMERIC; +import static io.r2dbc.postgresql.codec.PostgresqlObjectId.VARCHAR; +import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY; +import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT; +import static io.r2dbc.postgresql.util.TestByteBufAllocator.TEST; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class YearCodecTest { + + @Test + void constructorNoByteBufAllocator() { + assertThatIllegalArgumentException().isThrownBy(() -> new YearCodec(null)) + .withMessage("byteBufAllocator must not be null"); + } + + @Test + void decode() { + Year year = Year.now(); + + assertThat(new YearCodec(TEST).decode(TEST.buffer().writeInt(year.getValue()), INT4, FORMAT_BINARY, Year.class)) + .isEqualTo(year); + } + + @Test + void decodeNoByteBuf() { + assertThat(new YearCodec(TEST).decode(null, INT4.getObjectId(), FORMAT_BINARY, Year.class)).isNull(); + } + + @Test + void doCanDecode() { + YearCodec codec = new YearCodec(TEST); + + assertThat(codec.doCanDecode(INT4, FORMAT_BINARY)).isTrue(); + assertThat(codec.doCanDecode(INT2, FORMAT_BINARY)).isTrue(); + assertThat(codec.doCanDecode(INT8, FORMAT_BINARY)).isTrue(); + assertThat(codec.doCanDecode(NUMERIC, FORMAT_TEXT)).isTrue(); + assertThat(codec.doCanDecode(VARCHAR, FORMAT_TEXT)).isFalse(); + } + + @Test + void doCanDecodeNoType() { + assertThatIllegalArgumentException().isThrownBy(() -> new YearCodec(TEST).doCanDecode(null, FORMAT_BINARY)) + .withMessage("type must not be null"); + } + + @Test + void doEncodeInt() { + Year year = Year.now(); + + ParameterAssert.assertThat(new YearCodec(TEST).doEncode(year)) + .hasFormat(FORMAT_BINARY) + .hasType(INT4.getObjectId()) + .hasValue(TEST.buffer().writeInt(year.getValue())); + } + + @Test + void doEncodeLong() { + Year year = Year.now(); + + ParameterAssert.assertThat(new YearCodec(TEST).doEncode(year, INT8)) + .hasFormat(FORMAT_BINARY) + .hasType(INT8.getObjectId()) + .hasValue(TEST.buffer().writeLong(year.getValue())); + } + + @Test + void doEncodeNoValue() { + assertThatIllegalArgumentException().isThrownBy(() -> new YearCodec(TEST).doEncode(null)) + .withMessage("value must not be null"); + } + + @Test + void doEncodeShort() { + Year year = Year.now(); + + ParameterAssert.assertThat(new YearCodec(TEST).doEncode(year, INT2)) + .hasFormat(FORMAT_BINARY) + .hasType(INT2.getObjectId()) + .hasValue(TEST.buffer().writeShort(year.getValue())); + } + + @Test + void encodeItemNoValue() { + assertThatIllegalArgumentException().isThrownBy(() -> new YearCodec(TEST).encode(null)) + .withMessage("value must not be null"); + } + + @Test + void encodeNull() { + ParameterAssert.assertThat(new YearCodec(TEST).encodeNull()) + .isEqualTo(new EncodedParameter(FORMAT_BINARY, INT4.getObjectId(), NULL_VALUE)); + } + + @Test + void myTest() { + + } + +} \ No newline at end of file