org.postgresql.jdbc.ArrayDecoding Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jdbc-yugabytedb Show documentation
Show all versions of jdbc-yugabytedb Show documentation
Java JDBC 4.2 (JRE 8+) driver for YugaByte SQL database
/*
* Copyright (c) 2020, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.jdbc;
import org.postgresql.core.BaseConnection;
import org.postgresql.core.Oid;
import org.postgresql.core.Parser;
import org.postgresql.jdbc2.ArrayAssistant;
import org.postgresql.jdbc2.ArrayAssistantRegistry;
import org.postgresql.util.GT;
import org.postgresql.util.PGbytea;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Utility for decoding arrays.
*
*
* See {@code ArrayEncoding} for description of the binary format of arrays.
*
*
* @author Brett Okken
*/
final class ArrayDecoding {
/**
* Array list implementation specific for storing PG array elements. If
* {@link PgArrayList#dimensionsCount} is {@code 1}, the contents will be
* {@link String}. For all larger dimensionsCount, the values will be
* {@link PgArrayList} instances.
*/
static final class PgArrayList extends ArrayList<@Nullable Object> {
private static final long serialVersionUID = 1L;
/**
* How many dimensions.
*/
int dimensionsCount = 1;
}
private interface ArrayDecoder {
A createArray(@NonNegative int size);
Object[] createMultiDimensionalArray(@NonNegative int[] sizes);
boolean supportBinary();
void populateFromBinary(A array, @NonNegative int index, @NonNegative int count, ByteBuffer bytes, BaseConnection connection)
throws SQLException;
void populateFromString(A array, List<@Nullable String> strings, BaseConnection connection) throws SQLException;
}
private abstract static class AbstractObjectStringArrayDecoder implements ArrayDecoder {
final Class> baseClazz;
AbstractObjectStringArrayDecoder(Class> baseClazz) {
this.baseClazz = baseClazz;
}
/**
* {@inheritDoc}
*/
@Override
public boolean supportBinary() {
return false;
}
@SuppressWarnings("unchecked")
@Override
public A createArray(int size) {
return (A) Array.newInstance(baseClazz, size);
}
/**
* {@inheritDoc}
*/
@Override
public Object[] createMultiDimensionalArray(int[] sizes) {
return (Object[]) Array.newInstance(baseClazz, sizes);
}
@Override
public void populateFromBinary(A arr, int index, int count, ByteBuffer bytes, BaseConnection connection)
throws SQLException {
throw new SQLFeatureNotSupportedException();
}
/**
* {@inheritDoc}
*/
@Override
public void populateFromString(A arr, List<@Nullable String> strings, BaseConnection connection) throws SQLException {
final @Nullable Object[] array = (Object[]) arr;
for (int i = 0, j = strings.size(); i < j; ++i) {
final String stringVal = strings.get(i);
array[i] = stringVal != null ? parseValue(stringVal, connection) : null;
}
}
abstract Object parseValue(String stringVal, BaseConnection connection) throws SQLException;
}
private abstract static class AbstractObjectArrayDecoder extends AbstractObjectStringArrayDecoder {
AbstractObjectArrayDecoder(Class> baseClazz) {
super(baseClazz);
}
/**
* {@inheritDoc}
*/
@Override
public boolean supportBinary() {
return true;
}
@Override
public void populateFromBinary(A arr, @NonNegative int index, @NonNegative int count, ByteBuffer bytes, BaseConnection connection)
throws SQLException {
final @Nullable Object[] array = (Object[]) arr;
// skip through to the requested index
for (int i = 0; i < index; ++i) {
final int length = bytes.getInt();
if (length > 0) {
bytes.position(bytes.position() + length);
}
}
for (int i = 0; i < count; ++i) {
final int length = bytes.getInt();
if (length != -1) {
array[i] = parseValue(length, bytes, connection);
} else {
// explicitly set to null for reader's clarity
array[i] = null;
}
}
}
abstract Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) throws SQLException;
}
private static final ArrayDecoder LONG_OBJ_ARRAY = new AbstractObjectArrayDecoder(Long.class) {
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) {
return bytes.getLong();
}
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return PgResultSet.toLong(stringVal);
}
};
private static final ArrayDecoder INT4_UNSIGNED_OBJ_ARRAY = new AbstractObjectArrayDecoder(
Long.class) {
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) {
final long value = bytes.getInt() & 0xFFFFFFFFL;
return value;
}
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return PgResultSet.toLong(stringVal);
}
};
private static final ArrayDecoder INTEGER_OBJ_ARRAY = new AbstractObjectArrayDecoder(
Integer.class) {
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) {
return bytes.getInt();
}
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return PgResultSet.toInt(stringVal);
}
};
private static final ArrayDecoder SHORT_OBJ_ARRAY = new AbstractObjectArrayDecoder(Short.class) {
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) {
return bytes.getShort();
}
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return PgResultSet.toShort(stringVal);
}
};
private static final ArrayDecoder DOUBLE_OBJ_ARRAY = new AbstractObjectArrayDecoder(
Double.class) {
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) {
return bytes.getDouble();
}
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return PgResultSet.toDouble(stringVal);
}
};
private static final ArrayDecoder FLOAT_OBJ_ARRAY = new AbstractObjectArrayDecoder(Float.class) {
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) {
return bytes.getFloat();
}
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return PgResultSet.toFloat(stringVal);
}
};
private static final ArrayDecoder BOOLEAN_OBJ_ARRAY = new AbstractObjectArrayDecoder(
Boolean.class) {
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) {
return bytes.get() == 1;
}
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return BooleanTypeUtil.fromString(stringVal);
}
};
private static final ArrayDecoder STRING_ARRAY = new AbstractObjectArrayDecoder(String.class) {
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) throws SQLException {
assert bytes.hasArray();
final byte[] byteArray = bytes.array();
final int offset = bytes.arrayOffset() + bytes.position();
String val;
try {
val = connection.getEncoding().decode(byteArray, offset, length);
} catch (IOException e) {
throw new PSQLException(GT.tr(
"Invalid character data was found. This is most likely caused by stored data containing characters that are invalid for the character set the database was created in. The most common example of this is storing 8bit data in a SQL_ASCII database."),
PSQLState.DATA_ERROR, e);
}
bytes.position(bytes.position() + length);
return val;
}
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return stringVal;
}
};
private static final ArrayDecoder BYTE_ARRAY_ARRAY = new AbstractObjectArrayDecoder(
byte[].class) {
/**
* {@inheritDoc}
*/
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) throws SQLException {
final byte[] array = new byte[length];
bytes.get(array);
return array;
}
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return PGbytea.toBytes(stringVal.getBytes(StandardCharsets.US_ASCII));
}
};
private static final ArrayDecoder BIG_DECIMAL_STRING_DECODER = new AbstractObjectStringArrayDecoder(
BigDecimal.class) {
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return PgResultSet.toBigDecimal(stringVal);
}
};
private static final ArrayDecoder STRING_ONLY_DECODER = new AbstractObjectStringArrayDecoder(
String.class) {
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return stringVal;
}
};
private static final ArrayDecoder DATE_DECODER = new AbstractObjectStringArrayDecoder(
java.sql.Date.class) {
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return connection.getTimestampUtils().toDate(null, stringVal);
}
};
private static final ArrayDecoder TIME_DECODER = new AbstractObjectStringArrayDecoder(
java.sql.Time.class) {
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return connection.getTimestampUtils().toTime(null, stringVal);
}
};
private static final ArrayDecoder TIMESTAMP_DECODER = new AbstractObjectStringArrayDecoder(
java.sql.Timestamp.class) {
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return connection.getTimestampUtils().toTimestamp(null, stringVal);
}
};
/**
* Maps from base type oid to {@link ArrayDecoder} capable of processing
* entries.
*/
@SuppressWarnings("rawtypes")
private static final Map OID_TO_DECODER = new HashMap(
(int) (21 / .75) + 1);
static {
OID_TO_DECODER.put(Oid.OID, INT4_UNSIGNED_OBJ_ARRAY);
OID_TO_DECODER.put(Oid.INT8, LONG_OBJ_ARRAY);
OID_TO_DECODER.put(Oid.INT4, INTEGER_OBJ_ARRAY);
OID_TO_DECODER.put(Oid.INT2, SHORT_OBJ_ARRAY);
OID_TO_DECODER.put(Oid.MONEY, DOUBLE_OBJ_ARRAY);
OID_TO_DECODER.put(Oid.FLOAT8, DOUBLE_OBJ_ARRAY);
OID_TO_DECODER.put(Oid.FLOAT4, FLOAT_OBJ_ARRAY);
OID_TO_DECODER.put(Oid.TEXT, STRING_ARRAY);
OID_TO_DECODER.put(Oid.VARCHAR, STRING_ARRAY);
// 42.2.x decodes jsonb array as String rather than PGobject
OID_TO_DECODER.put(Oid.JSONB, STRING_ONLY_DECODER);
OID_TO_DECODER.put(Oid.BIT, BOOLEAN_OBJ_ARRAY);
OID_TO_DECODER.put(Oid.BOOL, BOOLEAN_OBJ_ARRAY);
OID_TO_DECODER.put(Oid.BYTEA, BYTE_ARRAY_ARRAY);
OID_TO_DECODER.put(Oid.NUMERIC, BIG_DECIMAL_STRING_DECODER);
OID_TO_DECODER.put(Oid.BPCHAR, STRING_ONLY_DECODER);
OID_TO_DECODER.put(Oid.CHAR, STRING_ONLY_DECODER);
OID_TO_DECODER.put(Oid.JSON, STRING_ONLY_DECODER);
OID_TO_DECODER.put(Oid.DATE, DATE_DECODER);
OID_TO_DECODER.put(Oid.TIME, TIME_DECODER);
OID_TO_DECODER.put(Oid.TIMETZ, TIME_DECODER);
OID_TO_DECODER.put(Oid.TIMESTAMP, TIMESTAMP_DECODER);
OID_TO_DECODER.put(Oid.TIMESTAMPTZ, TIMESTAMP_DECODER);
}
@SuppressWarnings("rawtypes")
private static final class ArrayAssistantObjectArrayDecoder extends AbstractObjectArrayDecoder {
private final ArrayAssistant arrayAssistant;
@SuppressWarnings("unchecked")
ArrayAssistantObjectArrayDecoder(ArrayAssistant arrayAssistant) {
super(arrayAssistant.baseType());
this.arrayAssistant = arrayAssistant;
}
/**
* {@inheritDoc}
*/
@Override
Object parseValue(int length, ByteBuffer bytes, BaseConnection connection) throws SQLException {
assert bytes.hasArray();
final byte[] byteArray = bytes.array();
final int offset = bytes.arrayOffset() + bytes.position();
final Object val = arrayAssistant.buildElement(byteArray, offset, length);
bytes.position(bytes.position() + length);
return val;
}
/**
* {@inheritDoc}
*/
@Override
Object parseValue(String stringVal, BaseConnection connection) throws SQLException {
return arrayAssistant.buildElement(stringVal);
}
}
private static final class MappedTypeObjectArrayDecoder extends AbstractObjectArrayDecoder