support) {
+ super();
+ this.support = support;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getDefaultArrayTypeOid() {
+ return support.getDefaultArrayTypeOid();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toArrayString(char delim, A @NonNull[] array) {
+ final StringBuilder sb = new StringBuilder(1024);
+ sb.append('{');
+ for (int i = 0; i < array.length; ++i) {
+ if (i > 0) {
+ sb.append(delim);
+ }
+ support.appendArray(sb, delim, array[i]);
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean supportBinaryRepresentation(int oid) {
+ return support.supportBinaryRepresentation(oid);
+ }
+
+ /**
+ * {@inheritDoc} 4 bytes - dimension 4 bytes - oid 4 bytes - ? 8*d bytes -
+ * dimension length
+ */
+ @Override
+ public byte[] toBinaryRepresentation(BaseConnection connection, A[] array, int oid)
+ throws SQLException, SQLFeatureNotSupportedException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.min(1024, (array.length * 32) + 20));
+ final byte[] buffer = new byte[4];
+
+ boolean hasNulls = false;
+ for (int i = 0; !hasNulls && i < array.length; ++i) {
+ if (support.countNulls(array[i]) > 0) {
+ hasNulls = true;
+ }
+ }
+
+ try {
+ // 2 dimension
+ ByteConverter.int4(buffer, 0, 2);
+ baos.write(buffer);
+ // nulls
+ ByteConverter.int4(buffer, 0, hasNulls ? 1 : 0);
+ baos.write(buffer);
+ // oid
+ ByteConverter.int4(buffer, 0, support.getTypeOID(oid));
+ baos.write(buffer);
+
+ // length
+ ByteConverter.int4(buffer, 0, array.length);
+ baos.write(buffer);
+ // write 4 empty bytes
+ java.util.Arrays.fill(buffer, (byte) 0);
+ baos.write(buffer);
+
+ ByteConverter.int4(buffer, 0, array.length > 0 ? Array.getLength(array[0]) : 0);
+ baos.write(buffer);
+ // write 4 empty bytes
+ java.util.Arrays.fill(buffer, (byte) 0);
+ baos.write(buffer);
+
+ for (int i = 0; i < array.length; ++i) {
+ baos.write(support.toSingleDimensionBinaryRepresentation(connection, array[i]));
+ }
+
+ return baos.toByteArray();
+
+ } catch (IOException e) {
+ // this IO exception is from writing to baos, which will never throw an
+ // IOException
+ throw new java.lang.AssertionError(e);
+ }
+ }
+ }
+
+ /**
+ * Wraps an {@link AbstractArrayEncoder} implementation and provides support for
+ * 2 or more dimensions using recursion.
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private static final class RecursiveArrayEncoder implements ArrayEncoder {
+
+ private final AbstractArrayEncoder support;
+ private final @Positive int dimensions;
+
+ /**
+ * @param support
+ * The instance providing support for the base array type.
+ */
+ RecursiveArrayEncoder(AbstractArrayEncoder support, @Positive int dimensions) {
+ super();
+ this.support = support;
+ this.dimensions = dimensions;
+ assert dimensions >= 2;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getDefaultArrayTypeOid() {
+ return support.getDefaultArrayTypeOid();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toArrayString(char delim, Object array) {
+ final StringBuilder sb = new StringBuilder(2048);
+ arrayString(sb, array, delim, dimensions);
+ return sb.toString();
+ }
+
+ private void arrayString(StringBuilder sb, Object array, char delim, int depth) {
+
+ if (depth > 1) {
+ sb.append('{');
+ for (int i = 0, j = Array.getLength(array); i < j; ++i) {
+ if (i > 0) {
+ sb.append(delim);
+ }
+ arrayString(sb, Array.get(array, i), delim, depth - 1);
+ }
+ sb.append('}');
+ } else {
+ support.appendArray(sb, delim, array);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean supportBinaryRepresentation(int oid) {
+ return support.supportBinaryRepresentation(oid);
+ }
+
+ private boolean hasNulls(Object array, int depth) {
+ if (depth > 1) {
+ for (int i = 0, j = Array.getLength(array); i < j; ++i) {
+ if (hasNulls(Array.get(array, i), depth - 1)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return support.countNulls(array) > 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public byte[] toBinaryRepresentation(BaseConnection connection, Object array, int oid)
+ throws SQLException, SQLFeatureNotSupportedException {
+
+ final boolean hasNulls = hasNulls(array, dimensions);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * dimensions);
+ final byte[] buffer = new byte[4];
+
+ try {
+ // dimensions
+ ByteConverter.int4(buffer, 0, dimensions);
+ baos.write(buffer);
+ // nulls
+ ByteConverter.int4(buffer, 0, hasNulls ? 1 : 0);
+ baos.write(buffer);
+ // oid
+ ByteConverter.int4(buffer, 0, support.getTypeOID(oid));
+ baos.write(buffer);
+
+ // length
+ ByteConverter.int4(buffer, 0, Array.getLength(array));
+ baos.write(buffer);
+ // write 4 empty bytes for lower bounds value. this is
+ java.util.Arrays.fill(buffer, (byte) 0);
+ baos.write(buffer);
+
+ writeArray(connection, buffer, baos, array, dimensions, true);
+
+ return baos.toByteArray();
+
+ } catch (IOException e) {
+ // this IO exception is from writing to baos, which will never throw an
+ // IOException
+ throw new java.lang.AssertionError(e);
+ }
+ }
+
+ private void writeArray(BaseConnection connection, byte[] buffer, ByteArrayOutputStream baos,
+ Object array, int depth, boolean first) throws IOException, SQLException {
+ final int length = Array.getLength(array);
+
+ if (first) {
+ ByteConverter.int4(buffer, 0, length > 0 ? Array.getLength(Array.get(array, 0)) : 0);
+ baos.write(buffer);
+ // write 4 empty bytes
+ java.util.Arrays.fill(buffer, (byte) 0);
+ baos.write(buffer);
+ }
+
+ for (int i = 0; i < length; ++i) {
+ final Object subArray = Array.get(array, i);
+ if (depth > 2) {
+ writeArray(connection, buffer, baos, subArray, depth - 1, i == 0);
+ } else {
+ baos.write(support.toSingleDimensionBinaryRepresentation(connection, subArray));
+ }
+ }
+ }
+
+ }
+}
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java b/pgjdbc/src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java
index 84080db22e..b7b2225155 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java
@@ -51,7 +51,7 @@ static boolean castToBoolean(final Object in) throws PSQLException {
throw new PSQLException("Cannot cast to boolean", PSQLState.CANNOT_COERCE);
}
- private static boolean fromString(final String strval) throws PSQLException {
+ static boolean fromString(final String strval) throws PSQLException {
// Leading or trailing whitespace is ignored, and case does not matter.
final String val = strval.trim();
if ("1".equals(val) || "true".equalsIgnoreCase(val)
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
index 97c0731eac..15b011e8a3 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
@@ -9,11 +9,10 @@
import org.postgresql.core.BaseConnection;
import org.postgresql.core.BaseStatement;
-import org.postgresql.core.Encoding;
import org.postgresql.core.Field;
import org.postgresql.core.Oid;
import org.postgresql.core.Tuple;
-import org.postgresql.jdbc2.ArrayAssistant;
+import org.postgresql.jdbc.ArrayDecoding.PgArrayList;
import org.postgresql.jdbc2.ArrayAssistantRegistry;
import org.postgresql.util.ByteConverter;
import org.postgresql.util.GT;
@@ -22,15 +21,11 @@
import org.checkerframework.checker.nullness.qual.Nullable;
-import java.io.IOException;
-import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.logging.Level;
/**
* Array is used collect one column of query result data.
@@ -51,20 +46,6 @@ public class PgArray implements java.sql.Array {
ArrayAssistantRegistry.register(Oid.UUID_ARRAY, new UUIDArrayAssistant());
}
- /**
- * Array list implementation specific for storing PG array elements.
- */
- private static class PgArrayList extends ArrayList<@Nullable Object> {
-
- private static final long serialVersionUID = 2052783752654562677L;
-
- /**
- * How many dimensions.
- */
- int dimensionsCount = 1;
-
- }
-
/**
* A database connection.
*/
@@ -80,26 +61,17 @@ private static class PgArrayList extends ArrayList<@Nullable Object> {
*/
protected @Nullable String fieldString;
- /**
- * Whether Object[] should be used instead primitive arrays. Object[] can contain null elements.
- * It should be set to true
if
- * {@link BaseConnection#haveMinimumCompatibleVersion(String)} returns true
for
- * argument "8.3".
- */
- private final boolean useObjects;
-
/**
* Value of field as {@link PgArrayList}. Will be initialized only once within
- * {@link #buildArrayList()}.
+ * {@link #buildArrayList(String)}.
*/
- protected @Nullable PgArrayList arrayList;
+ protected ArrayDecoding.@Nullable PgArrayList arrayList;
protected byte @Nullable [] fieldBytes;
private PgArray(BaseConnection connection, int oid) throws SQLException {
this.connection = connection;
this.oid = oid;
- this.useObjects = true;
}
/**
@@ -182,14 +154,14 @@ public Object getArray(long index, int count, @Nullable Map> ma
return null;
}
- PgArrayList arrayList = buildArrayList();
+ final PgArrayList arrayList = buildArrayList(fieldString);
if (count == 0) {
count = arrayList.size();
}
// array index out of range
- if ((--index) + count > arrayList.size()) {
+ if ((index - 1) + count > arrayList.size()) {
throw new PSQLException(
GT.tr("The array index is out of range: {0}, number of elements: {1}.",
index + count, (long) arrayList.size()),
@@ -199,95 +171,8 @@ public Object getArray(long index, int count, @Nullable Map> ma
return buildArray(arrayList, (int) index, count);
}
- private Object readBinaryArray(byte[] fieldBytes, int index, int count)
- throws SQLException {
- int dimensions = ByteConverter.int4(fieldBytes, 0);
- // int flags = ByteConverter.int4(fieldBytes, 4); // bit 0: 0=no-nulls, 1=has-nulls
- int elementOid = ByteConverter.int4(fieldBytes, 8);
- int pos = 12;
- int[] dims = new int[dimensions];
- for (int d = 0; d < dimensions; ++d) {
- dims[d] = ByteConverter.int4(fieldBytes, pos);
- pos += 4;
- /* int lbound = ByteConverter.int4(fieldBytes, pos); */
- pos += 4;
- }
- if (dimensions == 0) {
- return java.lang.reflect.Array.newInstance(elementOidToClass(elementOid), 0);
- }
- if (count > 0) {
- dims[0] = Math.min(count, dims[0]);
- }
- Object arr = java.lang.reflect.Array.newInstance(elementOidToClass(elementOid), dims);
- try {
- storeValues(fieldBytes, (Object[]) arr, elementOid, dims, pos, 0, index);
- } catch (IOException ioe) {
- 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, ioe);
- }
- return arr;
- }
-
- private int storeValues(byte[] fieldBytes,
- final Object[] arr, int elementOid, final int[] dims, int pos,
- final int thisDimension, int index) throws SQLException, IOException {
- if (thisDimension == dims.length - 1) {
- for (int i = 1; i < index; ++i) {
- int len = ByteConverter.int4(fieldBytes, pos);
- pos += 4;
- if (len != -1) {
- pos += len;
- }
- }
- for (int i = 0; i < dims[thisDimension]; ++i) {
- int len = ByteConverter.int4(fieldBytes, pos);
- pos += 4;
- if (len == -1) {
- continue;
- }
- switch (elementOid) {
- case Oid.INT2:
- arr[i] = ByteConverter.int2(fieldBytes, pos);
- break;
- case Oid.INT4:
- arr[i] = ByteConverter.int4(fieldBytes, pos);
- break;
- case Oid.INT8:
- arr[i] = ByteConverter.int8(fieldBytes, pos);
- break;
- case Oid.FLOAT4:
- arr[i] = ByteConverter.float4(fieldBytes, pos);
- break;
- case Oid.FLOAT8:
- arr[i] = ByteConverter.float8(fieldBytes, pos);
- break;
- case Oid.NUMERIC:
- arr[i] = ByteConverter.numeric(fieldBytes, pos, len);
- break;
- case Oid.TEXT:
- case Oid.VARCHAR:
- Encoding encoding = getConnection().getEncoding();
- arr[i] = encoding.decode(fieldBytes, pos, len);
- break;
- case Oid.BOOL:
- arr[i] = ByteConverter.bool(fieldBytes, pos);
- break;
- default:
- ArrayAssistant arrAssistant = ArrayAssistantRegistry.getAssistant(elementOid);
- if (arrAssistant != null) {
- arr[i] = arrAssistant.buildElement(fieldBytes, pos, len);
- }
- }
- pos += len;
- }
- } else {
- for (int i = 0; i < dims[thisDimension]; ++i) {
- pos = storeValues(fieldBytes, (Object[]) arr[i], elementOid, dims, pos, thisDimension + 1, 0);
- }
- }
- return pos;
+ private Object readBinaryArray(byte[] fieldBytes, int index, int count) throws SQLException {
+ return ArrayDecoding.readBinaryArray(index, count, fieldBytes, getConnection());
}
private ResultSet readBinaryResultSet(byte[] fieldBytes, int index, int count)
@@ -403,157 +288,14 @@ private int calcRemainingDataLength(byte[] fieldBytes,
return pos;
}
- private Class> elementOidToClass(int oid) throws SQLException {
- switch (oid) {
- case Oid.INT2:
- return Short.class;
- case Oid.INT4:
- return Integer.class;
- case Oid.INT8:
- return Long.class;
- case Oid.FLOAT4:
- return Float.class;
- case Oid.FLOAT8:
- return Double.class;
- case Oid.NUMERIC:
- return BigDecimal.class;
- case Oid.TEXT:
- case Oid.VARCHAR:
- return String.class;
- case Oid.BOOL:
- return Boolean.class;
- default:
- ArrayAssistant arrElemBuilder = ArrayAssistantRegistry.getAssistant(oid);
- if (arrElemBuilder != null) {
- return arrElemBuilder.baseType();
- }
-
- throw org.postgresql.Driver.notImplemented(this.getClass(), "readBinaryArray(data,oid)");
- }
- }
-
/**
* Build {@link ArrayList} from field's string input. As a result of this method
* {@link #arrayList} is build. Method can be called many times in order to make sure that array
* list is ready to use, however {@link #arrayList} will be set only once during first call.
*/
- private synchronized PgArrayList buildArrayList() throws SQLException {
- PgArrayList arrayList = this.arrayList;
- if (arrayList != null) {
- return arrayList;
- }
-
- this.arrayList = arrayList = new PgArrayList();
-
- char delim = getConnection().getTypeInfo().getArrayDelimiter(oid);
-
- if (fieldString != null) {
-
- char[] chars = fieldString.toCharArray();
- StringBuilder buffer = null;
- boolean insideString = false;
- boolean wasInsideString = false; // needed for checking if NULL
- // value occurred
- List dims = new ArrayList(); // array dimension arrays
- PgArrayList curArray = arrayList; // currently processed array
-
- // Starting with 8.0 non-standard (beginning index
- // isn't 1) bounds the dimensions are returned in the
- // data formatted like so "[0:3]={0,1,2,3,4}".
- // Older versions simply do not return the bounds.
- //
- // Right now we ignore these bounds, but we could
- // consider allowing these index values to be used
- // even though the JDBC spec says 1 is the first
- // index. I'm not sure what a client would like
- // to see, so we just retain the old behavior.
- int startOffset = 0;
- {
- if (chars[0] == '[') {
- while (chars[startOffset] != '=') {
- startOffset++;
- }
- startOffset++; // skip =
- }
- }
-
- for (int i = startOffset; i < chars.length; i++) {
-
- // escape character that we need to skip
- if (chars[i] == '\\') {
- i++;
- } else if (!insideString && chars[i] == '{') {
- // subarray start
- if (dims.isEmpty()) {
- dims.add(arrayList);
- } else {
- PgArrayList a = new PgArrayList();
- PgArrayList p = dims.get(dims.size() - 1);
- p.add(a);
- dims.add(a);
- }
- curArray = dims.get(dims.size() - 1);
-
- // number of dimensions
- {
- for (int t = i + 1; t < chars.length; t++) {
- if (Character.isWhitespace(chars[t])) {
- continue;
- } else if (chars[t] == '{') {
- curArray.dimensionsCount++;
- } else {
- break;
- }
- }
- }
-
- buffer = new StringBuilder();
- continue;
- } else if (chars[i] == '"') {
- // quoted element
- insideString = !insideString;
- wasInsideString = true;
- continue;
- } else if (!insideString && Character.isWhitespace(chars[i])) {
- // white space
- continue;
- } else if ((!insideString && (chars[i] == delim || chars[i] == '}'))
- || i == chars.length - 1) {
- // array end or element end
- // when character that is a part of array element
- if (chars[i] != '"' && chars[i] != '}' && chars[i] != delim && buffer != null) {
- buffer.append(chars[i]);
- }
-
- String b = buffer == null ? null : buffer.toString();
-
- // add element to current array
- if (b != null && (!b.isEmpty() || wasInsideString)) {
- curArray.add(!wasInsideString && b.equals("NULL") ? null : b);
- }
-
- wasInsideString = false;
- buffer = new StringBuilder();
-
- // when end of an array
- if (chars[i] == '}') {
- dims.remove(dims.size() - 1);
-
- // when multi-dimension
- if (!dims.isEmpty()) {
- curArray = dims.get(dims.size() - 1);
- }
-
- buffer = null;
- }
-
- continue;
- }
-
- if (buffer != null) {
- buffer.append(chars[i]);
- }
- }
+ private synchronized PgArrayList buildArrayList(String fieldString) throws SQLException {
+ if (arrayList == null) {
+ arrayList = ArrayDecoding.buildArrayList(fieldString, getConnection().getTypeInfo().getArrayDelimiter(oid));
}
return arrayList;
}
@@ -563,269 +305,9 @@ private synchronized PgArrayList buildArrayList() throws SQLException {
*
* @param input list to be converted into array
*/
- private Object buildArray(PgArrayList input, int index, int count) throws SQLException {
-
- if (count < 0) {
- count = input.size();
- }
-
- // array to be returned
- Object ret = null;
-
- // how many dimensions
- int dims = input.dimensionsCount;
-
- // dimensions length array (to be used with java.lang.reflect.Array.newInstance(Class>,
- // int[]))
- int[] dimsLength = dims > 1 ? new int[dims] : null;
- if (dimsLength != null) {
- for (int i = 0; i < dims; i++) {
- dimsLength[i] = (i == 0 ? count : 0);
- }
- }
-
- // array elements counter
- int length = 0;
-
- // array elements type
- final int type =
- getConnection().getTypeInfo().getSQLType(
- getConnection().getTypeInfo().getPGArrayElement(oid));
-
- if (type == Types.BIT) {
- boolean[] pa = null; // primitive array
- @Nullable Object @Nullable [] oa = null; // objects array
-
- if (dimsLength != null || useObjects) {
- ret = oa = (dimsLength != null
- ? (Object[]) java.lang.reflect.Array
- .newInstance(useObjects ? Boolean.class : boolean.class, dimsLength)
- : new Boolean[count]);
- } else {
- ret = pa = new boolean[count];
- }
-
- // add elements
- for (; count > 0; count--) {
- Object o = input.get(index++);
-
- if (oa != null) {
- oa[length++] = o == null ? null
- : (dimsLength != null ? buildArray((PgArrayList) o, 0, -1) : BooleanTypeUtil.castToBoolean((String) o));
- } else if (pa != null) {
- pa[length++] = o != null && BooleanTypeUtil.castToBoolean(o);
- }
- }
- } else if (type == Types.SMALLINT) {
- short[] pa = null;
- @Nullable Object @Nullable [] oa = null;
-
- if (dimsLength != null || useObjects) {
- ret =
- oa = (dimsLength != null
- ? (Object[]) java.lang.reflect.Array
- .newInstance(useObjects ? Short.class : short.class, dimsLength)
- : new Short[count]);
- } else {
- ret = pa = new short[count];
- }
-
- for (; count > 0; count--) {
- Object o = input.get(index++);
-
- if (oa != null) {
- oa[length++] = o == null ? null
- : (dimsLength != null ? buildArray((PgArrayList) o, 0, -1) : PgResultSet.toShort((String) o));
- } else if (pa != null) {
- pa[length++] = o == null ? 0 : PgResultSet.toShort((String) o);
- }
- }
- } else if (type == Types.INTEGER) {
- int[] pa = null;
- @Nullable Object @Nullable [] oa = null;
-
- if (dimsLength != null || useObjects) {
- ret =
- oa = (dimsLength != null
- ? (Object[]) java.lang.reflect.Array
- .newInstance(useObjects ? Integer.class : int.class, dimsLength)
- : new Integer[count]);
- } else {
- ret = pa = new int[count];
- }
-
- for (; count > 0; count--) {
- Object o = input.get(index++);
-
- if (oa != null) {
- oa[length++] = o == null ? null
- : (dimsLength != null ? buildArray((PgArrayList) o, 0, -1) : PgResultSet.toInt((String) o));
- } else if (pa != null) {
- pa[length++] = o == null ? 0 : PgResultSet.toInt((String) o);
- }
- }
- } else if (type == Types.BIGINT) {
- long[] pa = null;
- @Nullable Object @Nullable [] oa = null;
-
- if (dimsLength != null || useObjects) {
- ret =
- oa = (dimsLength != null
- ? (Object[]) java.lang.reflect.Array
- .newInstance(useObjects ? Long.class : long.class, dimsLength)
- : new Long[count]);
- } else {
- ret = pa = new long[count];
- }
-
- for (; count > 0; count--) {
- Object o = input.get(index++);
-
- if (oa != null) {
- oa[length++] = o == null ? null
- : (dimsLength != null ? buildArray((PgArrayList) o, 0, -1) : PgResultSet.toLong((String) o));
- } else if (pa != null) {
- pa[length++] = o == null ? 0L : PgResultSet.toLong((String) o);
- }
- }
- } else if (type == Types.NUMERIC) {
- @Nullable Object[] oa;
- ret = oa =
- (dimsLength != null ? (Object[]) java.lang.reflect.Array.newInstance(BigDecimal.class, dimsLength)
- : new BigDecimal[count]);
-
- for (; count > 0; count--) {
- Object v = input.get(index++);
- oa[length++] = dimsLength != null && v != null ? buildArray((PgArrayList) v, 0, -1)
- : (v == null ? null : PgResultSet.toBigDecimal((String) v));
- }
- } else if (type == Types.REAL) {
- float[] pa = null;
- @Nullable Object @Nullable [] oa = null;
-
- if (dimsLength != null || useObjects) {
- ret =
- oa = (dimsLength != null
- ? (Object[]) java.lang.reflect.Array
- .newInstance(useObjects ? Float.class : float.class, dimsLength)
- : new Float[count]);
- } else {
- ret = pa = new float[count];
- }
-
- for (; count > 0; count--) {
- Object o = input.get(index++);
-
- if (oa != null) {
- oa[length++] = o == null ? null
- : (dimsLength != null ? buildArray((PgArrayList) o, 0, -1) : PgResultSet.toFloat((String) o));
- } else if (pa != null) {
- pa[length++] = o == null ? 0f : PgResultSet.toFloat((String) o);
- }
- }
- } else if (type == Types.DOUBLE) {
- double[] pa = null;
- @Nullable Object[] oa = null;
-
- if (dimsLength != null || useObjects) {
- ret = oa = (dimsLength != null
- ? (Object[]) java.lang.reflect.Array
- .newInstance(useObjects ? Double.class : double.class, dimsLength)
- : new Double[count]);
- } else {
- ret = pa = new double[count];
- }
-
- for (; count > 0; count--) {
- Object o = input.get(index++);
-
- if (oa != null) {
- oa[length++] = o == null ? null
- : (dimsLength != null ? buildArray((PgArrayList) o, 0, -1) : PgResultSet.toDouble((String) o));
- } else if (pa != null) {
- pa[length++] = o == null ? 0d : PgResultSet.toDouble((String) o);
- }
- }
- } else if (type == Types.CHAR || type == Types.VARCHAR || oid == Oid.JSONB_ARRAY) {
- @Nullable Object[] oa;
- ret =
- oa = (dimsLength != null ? (Object[]) java.lang.reflect.Array.newInstance(String.class, dimsLength)
- : new String[count]);
-
- for (; count > 0; count--) {
- Object v = input.get(index++);
- oa[length++] = dimsLength != null && v != null ? buildArray((PgArrayList) v, 0, -1) : v;
- }
- } else if (type == Types.DATE) {
- @Nullable Object[] oa;
- ret = oa = (dimsLength != null
- ? (Object[]) java.lang.reflect.Array.newInstance(java.sql.Date.class, dimsLength)
- : new java.sql.Date[count]);
-
- for (; count > 0; count--) {
- Object v = input.get(index++);
- oa[length++] = dimsLength != null && v != null ? buildArray((PgArrayList) v, 0, -1)
- : (v == null ? null : getConnection().getTimestampUtils().toDate(null, (String) v));
- }
- } else if (type == Types.TIME) {
- @Nullable Object[] oa;
- ret = oa = (dimsLength != null
- ? (Object[]) java.lang.reflect.Array.newInstance(java.sql.Time.class, dimsLength)
- : new java.sql.Time[count]);
-
- for (; count > 0; count--) {
- Object v = input.get(index++);
- oa[length++] = dimsLength != null && v != null ? buildArray((PgArrayList) v, 0, -1)
- : (v == null ? null : getConnection().getTimestampUtils().toTime(null, (String) v));
- }
- } else if (type == Types.TIMESTAMP) {
- @Nullable Object[] oa;
- ret = oa = (dimsLength != null
- ? (Object[]) java.lang.reflect.Array.newInstance(java.sql.Timestamp.class, dimsLength)
- : new java.sql.Timestamp[count]);
-
- for (; count > 0; count--) {
- Object v = input.get(index++);
- oa[length++] = dimsLength != null && v != null ? buildArray((PgArrayList) v, 0, -1)
- : (v == null ? null : getConnection().getTimestampUtils().toTimestamp(null, (String) v));
- }
- } else if (ArrayAssistantRegistry.getAssistant(oid) != null) {
- ArrayAssistant arrAssistant = castNonNull(ArrayAssistantRegistry.getAssistant(oid));
-
- @Nullable Object[] oa;
- ret = oa = (dimsLength != null)
- ? (Object[]) java.lang.reflect.Array.newInstance(arrAssistant.baseType(), dimsLength)
- : (Object[]) java.lang.reflect.Array.newInstance(arrAssistant.baseType(), count);
-
- for (; count > 0; count--) {
- Object v = input.get(index++);
- oa[length++] = (dimsLength != null && v != null) ? buildArray((PgArrayList) v, 0, -1)
- : (v == null ? null : arrAssistant.buildElement((String) v));
- }
- } else if (dims == 1) {
- @Nullable Object[] oa = new Object[count];
- String typeName = getBaseTypeName();
- for (; count > 0; count--) {
- Object v = input.get(index++);
- if (v instanceof String) {
- oa[length++] = getConnection().getObject(typeName, (String) v, null);
- } else if (v instanceof byte[]) {
- oa[length++] = getConnection().getObject(typeName, null, (byte[]) v);
- } else if (v == null) {
- oa[length++] = null;
- } else {
- throw org.postgresql.Driver.notImplemented(this.getClass(), "getArrayImpl(long,int,Map)");
- }
- }
- ret = oa;
- } else {
- // other datatypes not currently supported
- getConnection().getLogger().log(Level.FINEST, "getArrayImpl(long,int,Map) with {0}", getBaseTypeName());
-
- throw org.postgresql.Driver.notImplemented(this.getClass(), "getArrayImpl(long,int,Map)");
- }
-
- return ret;
+ private Object buildArray(ArrayDecoding.PgArrayList input, int index, int count) throws SQLException {
+ final BaseConnection connection = getConnection();
+ return ArrayDecoding.readStringArray(index, count, connection.getTypeInfo().getPGArrayElement(oid), input, connection);
}
public int getBaseType() throws SQLException {
@@ -833,7 +315,6 @@ public int getBaseType() throws SQLException {
}
public String getBaseTypeName() throws SQLException {
- buildArrayList();
int elementOID = getConnection().getTypeInfo().getPGArrayElement(oid);
return castNonNull(getConnection().getTypeInfo().getPGType(elementOID));
}
@@ -877,7 +358,7 @@ public ResultSet getResultSetImpl(long index, int count, @Nullable Map getSupportedBinaryOids() {
Oid.TIMETZ,
Oid.TIMESTAMP,
Oid.TIMESTAMPTZ,
+ Oid.BYTEA_ARRAY,
Oid.INT2_ARRAY,
Oid.INT4_ARRAY,
Oid.INT8_ARRAY,
+ Oid.OID_ARRAY,
Oid.FLOAT4_ARRAY,
Oid.FLOAT8_ARRAY,
Oid.VARCHAR_ARRAY,
@@ -384,6 +386,7 @@ private static Set getBinaryOids(Properties info) throws PSQLException
binaryOids.removeAll(getOidSet(oids));
}
binaryOids.retainAll(SUPPORTED_BINARY_OIDS);
+
return binaryOids;
}
@@ -1266,33 +1269,6 @@ public PGReplicationConnection getReplicationAPI() {
return new PGReplicationConnectionImpl(this);
}
- private static void appendArray(StringBuilder sb, Object elements, char delim) {
- sb.append('{');
-
- int nElements = java.lang.reflect.Array.getLength(elements);
- for (int i = 0; i < nElements; i++) {
- if (i > 0) {
- sb.append(delim);
- }
-
- Object o = java.lang.reflect.Array.get(elements, i);
- if (o == null) {
- sb.append("NULL");
- } else if (o.getClass().isArray()) {
- final PrimitiveArraySupport arraySupport = PrimitiveArraySupport.getArraySupport(o);
- if (arraySupport != null) {
- arraySupport.appendArray(sb, delim, o);
- } else {
- appendArray(sb, o, delim);
- }
- } else {
- String s = o.toString();
- PgArray.escapeArrayElement(sb, s);
- }
- }
- sb.append('}');
- }
-
// Parse a "dirty" integer surrounded by non-numeric characters
private static int integerPart(String dirtyString) {
int start = 0;
@@ -1397,6 +1373,7 @@ public Struct createStruct(String typeName, Object[] attributes) throws SQLExcep
throw org.postgresql.Driver.notImplemented(this.getClass(), "createStruct(String, Object[])");
}
+ @SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Array createArrayOf(String typeName, @Nullable Object elements) throws SQLException {
checkClosed();
@@ -1415,53 +1392,19 @@ public Array createArrayOf(String typeName, @Nullable Object elements) throws SQ
return makeArray(oid, null);
}
- final String arrayString;
-
- final PrimitiveArraySupport arraySupport = PrimitiveArraySupport.getArraySupport(elements);
-
- if (arraySupport != null) {
- // if the oid for the given type matches the default type, we might be
- // able to go straight to binary representation
- if (oid == arraySupport.getDefaultArrayTypeOid(typeInfo) && arraySupport.supportBinaryRepresentation()
- && getPreferQueryMode() != PreferQueryMode.SIMPLE) {
- return new PgArray(this, oid, arraySupport.toBinaryRepresentation(this, elements));
- }
- arrayString = arraySupport.toArrayString(delim, elements);
- } else {
- final Class> clazz = elements.getClass();
- if (!clazz.isArray()) {
- throw new PSQLException(GT.tr("Invalid elements {0}", elements), PSQLState.INVALID_PARAMETER_TYPE);
- }
- StringBuilder sb = new StringBuilder();
- appendArray(sb, elements, delim);
- arrayString = sb.toString();
+ final ArrayEncoding.ArrayEncoder arraySupport = ArrayEncoding.getArrayEncoder(elements);
+ if (arraySupport.supportBinaryRepresentation(oid) && getPreferQueryMode() != PreferQueryMode.SIMPLE) {
+ return new PgArray(this, oid, arraySupport.toBinaryRepresentation(this, elements, oid));
}
+ final String arrayString = arraySupport.toArrayString(delim, elements);
return makeArray(oid, arrayString);
}
@Override
public Array createArrayOf(String typeName, @Nullable Object @Nullable [] elements)
throws SQLException {
- checkClosed();
-
- int oid = getTypeInfo().getPGArrayType(typeName);
-
- if (oid == Oid.UNSPECIFIED) {
- throw new PSQLException(
- GT.tr("Unable to find server array type for provided name {0}.", typeName),
- PSQLState.INVALID_NAME);
- }
-
- if (elements == null) {
- return makeArray(oid, null);
- }
-
- char delim = getTypeInfo().getArrayDelimiter(oid);
- StringBuilder sb = new StringBuilder();
- appendArray(sb, elements, delim);
-
- return makeArray(oid, sb.toString());
+ return createArrayOf(typeName, (Object) elements);
}
@Override
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
index e265e2a0af..e78f700782 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
@@ -33,6 +33,7 @@
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.qual.IntRange;
@@ -59,6 +60,7 @@
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
@@ -70,6 +72,7 @@
import java.util.UUID;
class PgPreparedStatement extends PgStatement implements PreparedStatement {
+
protected final CachedQuery preparedQuery; // Query fragments for prepared statement.
protected final ParameterList preparedParameters; // Parameter values for prepared statement.
@@ -677,13 +680,14 @@ public void setObject(@Positive int parameterIndex, @Nullable Object in,
case Types.ARRAY:
if (in instanceof Array) {
setArray(parameterIndex, (Array) in);
- } else if (PrimitiveArraySupport.isSupportedPrimitiveArray(in)) {
- setPrimitiveArray(parameterIndex, in);
} else {
- throw new PSQLException(
- GT.tr("Cannot cast an instance of {0} to type {1}",
- in.getClass().getName(), "Types.ARRAY"),
- PSQLState.INVALID_PARAMETER_TYPE);
+ try {
+ setObjectArray(parameterIndex, in);
+ } catch (Exception e) {
+ throw new PSQLException(
+ GT.tr("Cannot cast an instance of {0} to type {1}", in.getClass().getName(), "Types.ARRAY"),
+ PSQLState.INVALID_PARAMETER_TYPE, e);
+ }
}
break;
case Types.DISTINCT:
@@ -704,20 +708,24 @@ public void setObject(@Positive int parameterIndex, @Nullable Object in,
}
}
- private void setPrimitiveArray(@Positive int parameterIndex, A in) throws SQLException {
- // TODO: move to method parameter?
- final PrimitiveArraySupport arrayToString =
- castNonNull(PrimitiveArraySupport.getArraySupport(in));
+ private void setObjectArray(int parameterIndex, A in) throws SQLException {
+ final ArrayEncoding.ArrayEncoder arraySupport = ArrayEncoding.getArrayEncoder(in);
final TypeInfo typeInfo = connection.getTypeInfo();
- final int oid = arrayToString.getDefaultArrayTypeOid(typeInfo);
+ final int oid = arraySupport.getDefaultArrayTypeOid();
- if (arrayToString.supportBinaryRepresentation() && connection.getPreferQueryMode() != PreferQueryMode.SIMPLE) {
- bindBytes(parameterIndex, arrayToString.toBinaryRepresentation(connection, in), oid);
+ if (arraySupport.supportBinaryRepresentation(oid) && connection.getPreferQueryMode() != PreferQueryMode.SIMPLE) {
+ bindBytes(parameterIndex, arraySupport.toBinaryRepresentation(connection, in, oid), oid);
} else {
- final char delim = typeInfo.getArrayDelimiter(oid);
- setString(parameterIndex, arrayToString.toArrayString(delim, in), oid);
+ if (oid == Oid.UNSPECIFIED) {
+ throw new SQLFeatureNotSupportedException();
+ }
+ final int baseOid = typeInfo.getPGArrayElement(oid);
+ final String baseType = castNonNull(typeInfo.getPGType(baseOid));
+
+ final Array array = getPGConnection().createArrayOf(baseType, in);
+ this.setArray(parameterIndex, array);
}
}
@@ -985,8 +993,14 @@ public void setObject(@Positive int parameterIndex, @Nullable Object x) throws S
setMap(parameterIndex, (Map, ?>) x);
} else if (x instanceof Number) {
setNumber(parameterIndex, (Number) x);
- } else if (PrimitiveArraySupport.isSupportedPrimitiveArray(x)) {
- setPrimitiveArray(parameterIndex, x);
+ } else if (x.getClass().isArray()) {
+ try {
+ setObjectArray(parameterIndex, x);
+ } catch (Exception e) {
+ throw new PSQLException(
+ GT.tr("Cannot cast an instance of {0} to type {1}", x.getClass().getName(), "Types.ARRAY"),
+ PSQLState.INVALID_PARAMETER_TYPE, e);
+ }
} else {
// Can't infer a type.
throw new PSQLException(GT.tr(
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PrimitiveArraySupport.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PrimitiveArraySupport.java
deleted file mode 100644
index 060672bdb1..0000000000
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PrimitiveArraySupport.java
+++ /dev/null
@@ -1,492 +0,0 @@
-/*
- * Copyright (c) 2004, PostgreSQL Global Development Group
- * See the LICENSE file in the project root for more information.
- */
-
-package org.postgresql.jdbc;
-
-import org.postgresql.core.Oid;
-import org.postgresql.core.TypeInfo;
-import org.postgresql.util.ByteConverter;
-
-import org.checkerframework.checker.nullness.qual.NonNull;
-import org.checkerframework.checker.nullness.qual.Nullable;
-
-import java.sql.Connection;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.HashMap;
-import java.util.Map;
-
-abstract class PrimitiveArraySupport {
-
- public abstract int getDefaultArrayTypeOid(TypeInfo tiCache);
-
- public abstract String toArrayString(char delim, A array);
-
- public abstract void appendArray(StringBuilder sb, char delim, A array);
-
- public boolean supportBinaryRepresentation() {
- return true;
- }
-
- public abstract byte[] toBinaryRepresentation(Connection connection, A array) throws SQLFeatureNotSupportedException;
-
- private static final PrimitiveArraySupport LONG_ARRAY = new PrimitiveArraySupport() {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getDefaultArrayTypeOid(TypeInfo tiCache) {
- return Oid.INT8_ARRAY;
- }
-
- @Override
- public String toArrayString(char delim, long[] array) {
- final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
- appendArray(sb, delim, array);
- return sb.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void appendArray(StringBuilder sb, char delim, long[] array) {
- sb.append('{');
- for (int i = 0; i < array.length; ++i) {
- if (i > 0) {
- sb.append(delim);
- }
- sb.append(array[i]);
- }
- sb.append('}');
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public byte[] toBinaryRepresentation(Connection connection, long[] array) {
-
- int length = 20 + (12 * array.length);
- final byte[] bytes = new byte[length];
-
- // 1 dimension
- ByteConverter.int4(bytes, 0, 1);
- // no null
- ByteConverter.int4(bytes, 4, 0);
- // oid
- ByteConverter.int4(bytes, 8, Oid.INT8);
- // length
- ByteConverter.int4(bytes, 12, array.length);
-
- int idx = 20;
- for (int i = 0; i < array.length; ++i) {
- bytes[idx + 3] = 8;
- ByteConverter.int8(bytes, idx + 4, array[i]);
- idx += 12;
- }
-
- return bytes;
- }
- };
-
- private static final PrimitiveArraySupport INT_ARRAY = new PrimitiveArraySupport() {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getDefaultArrayTypeOid(TypeInfo tiCache) {
- return Oid.INT4_ARRAY;
- }
-
- @Override
- public String toArrayString(char delim, int[] array) {
- final StringBuilder sb = new StringBuilder(Math.max(32, array.length * 6));
- appendArray(sb, delim, array);
- return sb.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void appendArray(StringBuilder sb, char delim, int[] array) {
- sb.append('{');
- for (int i = 0; i < array.length; ++i) {
- if (i > 0) {
- sb.append(delim);
- }
- sb.append(array[i]);
- }
- sb.append('}');
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public byte[] toBinaryRepresentation(Connection connection, int[] array) {
-
- int length = 20 + (8 * array.length);
- final byte[] bytes = new byte[length];
-
- // 1 dimension
- ByteConverter.int4(bytes, 0, 1);
- // no null
- ByteConverter.int4(bytes, 4, 0);
- // oid
- ByteConverter.int4(bytes, 8, Oid.INT4);
- // length
- ByteConverter.int4(bytes, 12, array.length);
-
- int idx = 20;
- for (int i = 0; i < array.length; ++i) {
- bytes[idx + 3] = 4;
- ByteConverter.int4(bytes, idx + 4, array[i]);
- idx += 8;
- }
-
- return bytes;
- }
- };
-
- private static final PrimitiveArraySupport SHORT_ARRAY = new PrimitiveArraySupport() {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getDefaultArrayTypeOid(TypeInfo tiCache) {
- return Oid.INT2_ARRAY;
- }
-
- @Override
- public String toArrayString(char delim, short[] array) {
- final StringBuilder sb = new StringBuilder(Math.max(32, array.length * 4));
- appendArray(sb, delim, array);
- return sb.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void appendArray(StringBuilder sb, char delim, short[] array) {
- sb.append('{');
- for (int i = 0; i < array.length; ++i) {
- if (i > 0) {
- sb.append(delim);
- }
- sb.append(array[i]);
- }
- sb.append('}');
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public byte[] toBinaryRepresentation(Connection connection, short[] array) {
-
- int length = 20 + (6 * array.length);
- final byte[] bytes = new byte[length];
-
- // 1 dimension
- ByteConverter.int4(bytes, 0, 1);
- // no null
- ByteConverter.int4(bytes, 4, 0);
- // oid
- ByteConverter.int4(bytes, 8, Oid.INT2);
- // length
- ByteConverter.int4(bytes, 12, array.length);
-
- int idx = 20;
- for (int i = 0; i < array.length; ++i) {
- bytes[idx + 3] = 2;
- ByteConverter.int2(bytes, idx + 4, array[i]);
- idx += 6;
- }
-
- return bytes;
- }
-
- };
-
- private static final PrimitiveArraySupport DOUBLE_ARRAY = new PrimitiveArraySupport() {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getDefaultArrayTypeOid(TypeInfo tiCache) {
- return Oid.FLOAT8_ARRAY;
- }
-
- @Override
- public String toArrayString(char delim, double[] array) {
- final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
- appendArray(sb, delim, array);
- return sb.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void appendArray(StringBuilder sb, char delim, double[] array) {
- sb.append('{');
- for (int i = 0; i < array.length; ++i) {
- if (i > 0) {
- sb.append(delim);
- }
- // use quotes to account for any issues with scientific notation
- sb.append('"');
- sb.append(array[i]);
- sb.append('"');
- }
- sb.append('}');
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public byte[] toBinaryRepresentation(Connection connection, double[] array) {
-
- int length = 20 + (12 * array.length);
- final byte[] bytes = new byte[length];
-
- // 1 dimension
- ByteConverter.int4(bytes, 0, 1);
- // no null
- ByteConverter.int4(bytes, 4, 0);
- // oid
- ByteConverter.int4(bytes, 8, Oid.FLOAT8);
- // length
- ByteConverter.int4(bytes, 12, array.length);
-
- int idx = 20;
- for (int i = 0; i < array.length; ++i) {
- bytes[idx + 3] = 8;
- ByteConverter.float8(bytes, idx + 4, array[i]);
- idx += 12;
- }
-
- return bytes;
- }
-
- };
-
- private static final PrimitiveArraySupport FLOAT_ARRAY = new PrimitiveArraySupport() {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getDefaultArrayTypeOid(TypeInfo tiCache) {
- return Oid.FLOAT4_ARRAY;
- }
-
- @Override
- public String toArrayString(char delim, float[] array) {
- final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
- appendArray(sb, delim, array);
- return sb.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void appendArray(StringBuilder sb, char delim, float[] array) {
- sb.append('{');
- for (int i = 0; i < array.length; ++i) {
- if (i > 0) {
- sb.append(delim);
- }
- // use quotes to account for any issues with scientific notation
- sb.append('"');
- sb.append(array[i]);
- sb.append('"');
- }
- sb.append('}');
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public byte[] toBinaryRepresentation(Connection connection, float[] array) {
-
- int length = 20 + (8 * array.length);
- final byte[] bytes = new byte[length];
-
- // 1 dimension
- ByteConverter.int4(bytes, 0, 1);
- // no null
- ByteConverter.int4(bytes, 4, 0);
- // oid
- ByteConverter.int4(bytes, 8, Oid.FLOAT4);
- // length
- ByteConverter.int4(bytes, 12, array.length);
-
- int idx = 20;
- for (int i = 0; i < array.length; ++i) {
- bytes[idx + 3] = 4;
- ByteConverter.float4(bytes, idx + 4, array[i]);
- idx += 8;
- }
-
- return bytes;
- }
-
- };
-
- private static final PrimitiveArraySupport BOOLEAN_ARRAY = new PrimitiveArraySupport() {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getDefaultArrayTypeOid(TypeInfo tiCache) {
- return Oid.BOOL_ARRAY;
- }
-
- @Override
- public String toArrayString(char delim, boolean[] array) {
- final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
- appendArray(sb, delim, array);
- return sb.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void appendArray(StringBuilder sb, char delim, boolean[] array) {
- sb.append('{');
- for (int i = 0; i < array.length; ++i) {
- if (i > 0) {
- sb.append(delim);
- }
- sb.append(array[i] ? '1' : '0');
- }
- sb.append('}');
- }
-
- /**
- * {@inheritDoc}
- *
- * @throws SQLFeatureNotSupportedException
- * Because this feature is not supported.
- */
- @Override
- public byte[] toBinaryRepresentation(Connection connection, boolean[] array) throws SQLFeatureNotSupportedException {
- int length = 20 + (5 * array.length);
- final byte[] bytes = new byte[length];
-
- // 1 dimension
- ByteConverter.int4(bytes, 0, 1);
- // no null
- ByteConverter.int4(bytes, 4, 0);
- // oid
- ByteConverter.int4(bytes, 8, Oid.BOOL);
- // length
- ByteConverter.int4(bytes, 12, array.length);
-
- int idx = 20;
- for (int i = 0; i < array.length; ++i) {
- bytes[idx + 3] = 1;
- ByteConverter.bool(bytes, idx + 4, array[i]);
- idx += 5;
- }
-
- return bytes;
- }
-
- };
-
- private static final PrimitiveArraySupport STRING_ARRAY = new PrimitiveArraySupport() {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getDefaultArrayTypeOid(TypeInfo tiCache) {
- return Oid.VARCHAR_ARRAY;
- }
-
- @Override
- public String toArrayString(char delim, String[] array) {
- final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
- appendArray(sb, delim, array);
- return sb.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void appendArray(StringBuilder sb, char delim, String[] array) {
- sb.append('{');
- for (int i = 0; i < array.length; ++i) {
- if (i > 0) {
- sb.append(delim);
- }
- if (array[i] == null) {
- sb.append('N');
- sb.append('U');
- sb.append('L');
- sb.append('L');
- } else {
- PgArray.escapeArrayElement(sb, array[i]);
- }
- }
- sb.append('}');
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean supportBinaryRepresentation() {
- return false;
- }
-
- /**
- * {@inheritDoc}
- *
- * @throws SQLFeatureNotSupportedException
- * Because this feature is not supported.
- */
- @Override
- public byte[] toBinaryRepresentation(Connection connection, String[] array) throws SQLFeatureNotSupportedException {
- throw new SQLFeatureNotSupportedException();
- }
-
- };
-
- private static final Map ARRAY_CLASS_TO_SUPPORT = new HashMap((int) (7 / .75) + 1);
-
- static {
- ARRAY_CLASS_TO_SUPPORT.put(long[].class, LONG_ARRAY);
- ARRAY_CLASS_TO_SUPPORT.put(int[].class, INT_ARRAY);
- ARRAY_CLASS_TO_SUPPORT.put(short[].class, SHORT_ARRAY);
- ARRAY_CLASS_TO_SUPPORT.put(double[].class, DOUBLE_ARRAY);
- ARRAY_CLASS_TO_SUPPORT.put(float[].class, FLOAT_ARRAY);
- ARRAY_CLASS_TO_SUPPORT.put(boolean[].class, BOOLEAN_ARRAY);
- ARRAY_CLASS_TO_SUPPORT.put(String[].class, STRING_ARRAY);
- }
-
- public static boolean isSupportedPrimitiveArray(Object obj) {
- return obj != null && ARRAY_CLASS_TO_SUPPORT.containsKey(obj.getClass());
- }
-
- public static @Nullable PrimitiveArraySupport getArraySupport(
- A array) {
- return ARRAY_CLASS_TO_SUPPORT.get(array.getClass());
- }
-}
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc2/ArrayAssistantRegistry.java b/pgjdbc/src/main/java/org/postgresql/jdbc2/ArrayAssistantRegistry.java
index 9f8e607d49..9500436f3f 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc2/ArrayAssistantRegistry.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc2/ArrayAssistantRegistry.java
@@ -7,8 +7,8 @@
import org.checkerframework.checker.nullness.qual.Nullable;
-import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
/**
* Array assistants register here.
@@ -16,7 +16,7 @@
* @author Minglei Tu
*/
public class ArrayAssistantRegistry {
- private static final Map ARRAY_ASSISTANT_MAP =
+ private static final ConcurrentMap ARRAY_ASSISTANT_MAP =
new ConcurrentHashMap();
public static @Nullable ArrayAssistant getAssistant(int oid) {
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/AbstractArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/AbstractArraysTest.java
new file mode 100644
index 0000000000..f892712b28
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/AbstractArraysTest.java
@@ -0,0 +1,946 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import static org.junit.Assert.assertEquals;
+
+import org.postgresql.PGNotification;
+import org.postgresql.copy.CopyManager;
+import org.postgresql.core.BaseConnection;
+import org.postgresql.core.CachedQuery;
+import org.postgresql.core.Encoding;
+import org.postgresql.core.QueryExecutor;
+import org.postgresql.core.ReplicationProtocol;
+import org.postgresql.core.TransactionState;
+import org.postgresql.core.TypeInfo;
+import org.postgresql.core.Version;
+import org.postgresql.fastpath.Fastpath;
+import org.postgresql.jdbc.FieldMetadata.Key;
+import org.postgresql.largeobject.LargeObjectManager;
+import org.postgresql.replication.PGReplicationConnection;
+import org.postgresql.util.LruCache;
+import org.postgresql.util.PGobject;
+import org.postgresql.xml.PGXmlFactoryFactory;
+
+import org.junit.Test;
+
+import java.lang.reflect.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TimerTask;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+public abstract class AbstractArraysTest {
+
+ private static final BaseConnection ENCODING_CONNECTION = new EncodingConnection(Encoding.getJVMEncoding("utf-8"));
+
+ private final A[][] testData;
+
+ private final boolean binarySupported;
+
+ private final int arrayTypeOid;
+
+ /**
+ *
+ * @param testData
+ * 3 dimensional array to use for testing.
+ * @param binarySupported
+ * Indicates if binary support is epxected for the type.
+ */
+ public AbstractArraysTest(A[][] testData, boolean binarySupported, int arrayTypeOid) {
+ super();
+ this.testData = testData;
+ this.binarySupported = binarySupported;
+ this.arrayTypeOid = arrayTypeOid;
+ }
+
+ protected void assertArraysEquals(String message, A expected, Object actual) {
+ final int expectedLength = Array.getLength(expected);
+ assertEquals(message + " size", expectedLength, Array.getLength(actual));
+ for (int i = 0; i < expectedLength; ++i) {
+ assertEquals(message + " value at " + i, Array.get(expected, i), Array.get(actual, i));
+ }
+ }
+
+ @Test
+ public void testBinary() throws Exception {
+
+ A data = testData[0][0];
+
+ ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(data);
+
+ final int defaultArrayTypeOid = support.getDefaultArrayTypeOid();
+
+ assertEquals(binarySupported, support.supportBinaryRepresentation(defaultArrayTypeOid));
+
+ if (binarySupported) {
+
+ final PgArray pgArray = new PgArray(ENCODING_CONNECTION, defaultArrayTypeOid,
+ support.toBinaryRepresentation(ENCODING_CONNECTION, data, defaultArrayTypeOid));
+
+ Object actual = pgArray.getArray();
+
+ assertArraysEquals("", data, actual);
+ }
+ }
+
+ @Test
+ public void testString() throws Exception {
+
+ A data = testData[0][0];
+
+ ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(data);
+
+ final String arrayString = support.toArrayString(',', data);
+
+ final PgArray pgArray = new PgArray(ENCODING_CONNECTION, arrayTypeOid, arrayString);
+
+ Object actual = pgArray.getArray();
+
+ assertArraysEquals("", data, actual);
+ }
+
+ @Test
+ public void test2dBinary() throws Exception {
+
+ A[] data = testData[0];
+
+ ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(data);
+
+ final int defaultArrayTypeOid = support.getDefaultArrayTypeOid();
+
+ assertEquals(binarySupported, support.supportBinaryRepresentation(defaultArrayTypeOid));
+
+ if (binarySupported) {
+
+ final PgArray pgArray = new PgArray(ENCODING_CONNECTION, support.getDefaultArrayTypeOid(),
+ support.toBinaryRepresentation(ENCODING_CONNECTION, data, defaultArrayTypeOid));
+
+ Object[] actual = (Object[]) pgArray.getArray();
+
+ assertEquals(data.length, actual.length);
+
+ for (int i = 0; i < data.length; ++i) {
+ assertArraysEquals("array at position " + i, data[i], actual[i]);
+ }
+ }
+ }
+
+ @Test
+ public void test2dString() throws Exception {
+
+ final A[] data = testData[0];
+
+ final ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(data);
+
+ final String arrayString = support.toArrayString(',', data);
+
+ final PgArray pgArray = new PgArray(ENCODING_CONNECTION, arrayTypeOid, arrayString);
+
+ Object[] actual = (Object[]) pgArray.getArray();
+
+ assertEquals(data.length, actual.length);
+
+ for (int i = 0; i < data.length; ++i) {
+ assertArraysEquals("array at position " + i, data[i], actual[i]);
+ }
+ }
+
+ @Test
+ public void test3dBinary() throws Exception {
+
+ ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(testData);
+
+ final int defaultArrayTypeOid = support.getDefaultArrayTypeOid();
+
+ assertEquals(binarySupported, support.supportBinaryRepresentation(defaultArrayTypeOid));
+
+ if (binarySupported) {
+
+ final PgArray pgArray = new PgArray(ENCODING_CONNECTION, support.getDefaultArrayTypeOid(),
+ support.toBinaryRepresentation(ENCODING_CONNECTION, testData, defaultArrayTypeOid));
+
+ Object[][] actual = (Object[][]) pgArray.getArray();
+
+ assertEquals(testData.length, actual.length);
+
+ for (int i = 0; i < testData.length; ++i) {
+ assertEquals("array length at " + i, testData[i].length, actual[i].length);
+ for (int j = 0; j < testData[i].length; ++j) {
+ assertArraysEquals("array at " + i + ',' + j, testData[i][j], actual[i][j]);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void test3dString() throws Exception {
+
+ final ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(testData);
+
+ final String arrayString = support.toArrayString(',', testData);
+
+ final PgArray pgArray = new PgArray(ENCODING_CONNECTION, arrayTypeOid, arrayString);
+
+ Object[][] actual = (Object[][]) pgArray.getArray();
+
+ assertEquals(testData.length, actual.length);
+
+ for (int i = 0; i < testData.length; ++i) {
+ assertEquals("array length at " + i, testData[i].length, actual[i].length);
+ for (int j = 0; j < testData[i].length; ++j) {
+ assertArraysEquals("array at " + i + ',' + j, testData[i][j], actual[i][j]);
+ }
+ }
+ }
+
+ private static final class EncodingConnection implements BaseConnection {
+ private final Encoding encoding;
+ private final TypeInfo typeInfo = new TypeInfoCache(this, -1);
+
+ EncodingConnection(Encoding encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Encoding getEncoding() throws SQLException {
+ return encoding;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public TypeInfo getTypeInfo() {
+ return typeInfo;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void cancelQuery() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ResultSet execSQLQuery(String s) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ResultSet execSQLQuery(String s, int resultSetType, int resultSetConcurrency) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void execSQLUpdate(String s) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public QueryExecutor getQueryExecutor() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ReplicationProtocol getReplicationProtocol() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getObject(String type, String value, byte[] byteValue) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean haveMinimumServerVersion(int ver) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean haveMinimumServerVersion(Version ver) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] encodeString(String str) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String escapeString(String str) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean getStandardConformingStrings() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public TimestampUtils getTimestampUtils() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Logger getLogger() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean getStringVarcharFlag() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public TransactionState getTransactionState() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean binaryTransferSend(int oid) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isColumnSanitiserDisabled() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addTimerTask(TimerTask timerTask, long milliSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void purgeTimerTasks() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LruCache getFieldMetadataCache() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CachedQuery createQuery(String sql, boolean escapeProcessing, boolean isParameterized, String... columnNames)
+ throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setFlushCacheOnDeallocate(boolean flushCacheOnDeallocate) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Statement createStatement() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PreparedStatement prepareStatement(String sql) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CallableStatement prepareCall(String sql) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String nativeSQL(String sql) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setAutoCommit(boolean autoCommit) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean getAutoCommit() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void commit() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void rollback() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isClosed() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public DatabaseMetaData getMetaData() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setReadOnly(boolean readOnly) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isReadOnly() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setCatalog(String catalog) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getCatalog() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setTransactionIsolation(int level) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getTransactionIsolation() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SQLWarning getWarnings() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void clearWarnings() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
+ throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Map> getTypeMap() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setTypeMap(Map> map) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setHoldability(int holdability) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getHoldability() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Savepoint setSavepoint() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Savepoint setSavepoint(String name) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void rollback(Savepoint savepoint) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
+ throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
+ int resultSetHoldability) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
+ int resultSetHoldability) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Clob createClob() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Blob createBlob() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public NClob createNClob() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SQLXML createSQLXML() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isValid(int timeout) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setClientInfo(String name, String value) throws SQLClientInfoException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setClientInfo(Properties properties) throws SQLClientInfoException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getClientInfo(String name) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Properties getClientInfo() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public java.sql.Array createArrayOf(String typeName, Object[] elements) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setSchema(String schema) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getSchema() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void abort(Executor executor) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getNetworkTimeout() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public T unwrap(Class iface) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isWrapperFor(Class> iface) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public java.sql.Array createArrayOf(String typeName, Object elements) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PGNotification[] getNotifications() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PGNotification[] getNotifications(int timeoutMillis) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CopyManager getCopyAPI() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LargeObjectManager getLargeObjectAPI() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Fastpath getFastpathAPI() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addDataType(String type, String className) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addDataType(String type, Class extends PGobject> klass) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setPrepareThreshold(int threshold) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getPrepareThreshold() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setDefaultFetchSize(int fetchSize) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getDefaultFetchSize() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getBackendPID() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String escapeIdentifier(String identifier) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String escapeLiteral(String literal) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PreferQueryMode getPreferQueryMode() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public AutoSave getAutosave() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setAutosave(AutoSave autoSave) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PGReplicationConnection getReplicationAPI() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Map getParameterStatuses() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getParameterStatus(String parameterName) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hintReadOnly() {
+ return false;
+ }
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/ArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/ArraysTest.java
new file mode 100644
index 0000000000..bb93a3f4a4
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/ArraysTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import static org.junit.Assert.assertFalse;
+
+import org.postgresql.core.Oid;
+import org.postgresql.util.PSQLException;
+
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.sql.SQLFeatureNotSupportedException;
+
+public class ArraysTest {
+
+ @Test(expected = PSQLException.class)
+ public void testNonArrayNotSupported() throws Exception {
+ ArrayEncoding.getArrayEncoder("asdflkj");
+ }
+
+ @Test(expected = PSQLException.class)
+ public void testNoByteArray() throws Exception {
+ ArrayEncoding.getArrayEncoder(new byte[] {});
+ }
+
+ @Test(expected = SQLFeatureNotSupportedException.class)
+ public void testBinaryNotSupported() throws Exception {
+ final ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(new BigDecimal[] {});
+
+ assertFalse(support.supportBinaryRepresentation(Oid.FLOAT8_ARRAY));
+
+ support.toBinaryRepresentation(null, new BigDecimal[] { BigDecimal.valueOf(3) }, Oid.FLOAT8_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/ArraysTestSuite.java b/pgjdbc/src/test/java/org/postgresql/jdbc/ArraysTestSuite.java
new file mode 100644
index 0000000000..a34c8e8ef6
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/ArraysTestSuite.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ BigDecimalObjectArraysTest.class,
+ BooleanArraysTest.class,
+ BooleanObjectArraysTest.class,
+ ByteaArraysTest.class,
+ DoubleArraysTest.class,
+ DoubleObjectArraysTest.class,
+ FloatArraysTest.class,
+ FloatObjectArraysTest.class,
+ IntArraysTest.class,
+ IntegerObjectArraysTest.class,
+ LongArraysTest.class,
+ LongObjectArraysTest.class,
+ ShortArraysTest.class,
+ ShortObjectArraysTest.class,
+ StringArraysTest.class
+})
+public class ArraysTestSuite {
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/BigDecimalObjectArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/BigDecimalObjectArraysTest.java
new file mode 100644
index 0000000000..901da10626
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/BigDecimalObjectArraysTest.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import static java.math.BigDecimal.valueOf;
+
+import org.postgresql.core.Oid;
+
+import java.math.BigDecimal;
+
+public class BigDecimalObjectArraysTest extends AbstractArraysTest {
+
+ private static final BigDecimal[][][] doubles = new BigDecimal[][][] {
+ { { valueOf(1.3), valueOf(2.4), valueOf(3.1), valueOf(4.2) },
+ { valueOf(5D), valueOf(6D), valueOf(7D), valueOf(8D) },
+ { valueOf(9D), valueOf(10D), valueOf(11D), valueOf(12D) } },
+ { { valueOf(13D), valueOf(14D), valueOf(15D), valueOf(16D) }, { valueOf(17D), valueOf(18D), valueOf(19D), null },
+ { valueOf(21D), valueOf(22D), valueOf(23D), valueOf(24D) } } };
+
+ public BigDecimalObjectArraysTest() {
+ super(doubles, false, Oid.NUMERIC_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/BooleanArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/BooleanArraysTest.java
new file mode 100644
index 0000000000..c3717b5f4a
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/BooleanArraysTest.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class BooleanArraysTest extends AbstractArraysTest {
+ private static final boolean[][][] booleans = new boolean[][][] {
+ { { true, false, false, true }, { false, false, true, true }, { true, true, false, false } },
+ { { false, true, true, false }, { true, false, true, false }, { false, true, false, true } } };
+
+ public BooleanArraysTest() {
+ super(booleans, true, Oid.BOOL_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/BooleanObjectArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/BooleanObjectArraysTest.java
new file mode 100644
index 0000000000..6e33d1cd62
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/BooleanObjectArraysTest.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class BooleanObjectArraysTest extends AbstractArraysTest {
+ private static final Boolean[][][] booleans = new Boolean[][][] {
+ { { true, false, null, true }, { false, false, true, true }, { true, true, false, false } },
+ { { false, true, true, false }, { true, false, true, null }, { false, true, false, true } } };
+
+ public BooleanObjectArraysTest() {
+ super(booleans, true, Oid.BOOL_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/ByteaArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/ByteaArraysTest.java
new file mode 100644
index 0000000000..3a8141fa39
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/ByteaArraysTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import static org.junit.Assert.assertEquals;
+
+import org.postgresql.core.Oid;
+
+import org.junit.Assert;
+
+import java.lang.reflect.Array;
+
+public class ByteaArraysTest extends AbstractArraysTest {
+
+ private static final byte[][][][] longs = new byte[][][][] {
+ { { { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x5, 0x6, 0x7, (byte) 0xFF }, null, { 0x9, 0x10, 0x11, 0x12 } },
+ { null, { 0x13, 0x14, 0x15, 0x16 }, { 0x17, 0x18, (byte) 0xFF, 0x20 }, { 0x1, 0x2, (byte) 0xFF, 0x4 } },
+ { { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 },
+ { 0x1, 0x2, (byte) 0xFF, 0x4 } } },
+ { { { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 },
+ { 0x1, 0x2, (byte) 0xFE, 0x4 } },
+ { { 0x1, 0x2, (byte) 0xCD, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 },
+ { 0x1, 0x2, (byte) 0xFF, 0x4 } },
+ { { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFE, 0x10 }, { 0x1, 0x2, (byte) 0xFF, 0x4 },
+ { 0x1, 0x2, (byte) 0xFF, 0x4 } } } };
+
+ public ByteaArraysTest() {
+ super(longs, true, Oid.BYTEA_ARRAY);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void assertArraysEquals(String message, byte[][] expected, Object actual) {
+ final int expectedLength = Array.getLength(expected);
+ assertEquals(message + " size", expectedLength, Array.getLength(actual));
+ for (int i = 0; i < expectedLength; ++i) {
+ Assert.assertArrayEquals(message + " value at " + i, expected[i], (byte[]) Array.get(actual, i));
+ }
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/DoubleArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/DoubleArraysTest.java
new file mode 100644
index 0000000000..1f21f547d6
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/DoubleArraysTest.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class DoubleArraysTest extends AbstractArraysTest {
+
+ private static final double[][][] doubles = new double[][][] {
+ { { 1.2, 2.3, 3.7, 4.9 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } },
+ { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } };
+
+ public DoubleArraysTest() {
+ super(doubles, true, Oid.FLOAT8_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/DoubleObjectArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/DoubleObjectArraysTest.java
new file mode 100644
index 0000000000..90bc82036f
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/DoubleObjectArraysTest.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class DoubleObjectArraysTest extends AbstractArraysTest {
+
+ private static final Double[][][] doubles = new Double[][][] {
+ { { 1.3, 2.4, 3.1, 4.2 }, { 5D, 6D, 7D, 8D }, { 9D, 10D, 11D, 12D } },
+ { { 13D, 14D, 15D, 16D }, { 17D, 18D, 19D, null }, { 21D, 22D, 23D, 24D } } };
+
+ public DoubleObjectArraysTest() {
+ super(doubles, true, Oid.FLOAT8_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/FloatArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/FloatArraysTest.java
new file mode 100644
index 0000000000..ef9ed26ad7
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/FloatArraysTest.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class FloatArraysTest extends AbstractArraysTest {
+
+ private static final float[][][] floats = new float[][][] {
+ { { 1.2f, 2.3f, 3.7f, 4.9f }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } },
+ { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } };
+
+ public FloatArraysTest() {
+ super(floats, true, Oid.FLOAT4_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/FloatObjectArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/FloatObjectArraysTest.java
new file mode 100644
index 0000000000..c9a4a5f989
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/FloatObjectArraysTest.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class FloatObjectArraysTest extends AbstractArraysTest {
+
+ private static final Float[][][] floats = new Float[][][] {
+ { { 1.3f, 2.4f, 3.1f, 4.2f }, { 5f, 6f, 7f, 8f }, { 9f, 10f, 11f, 12f } },
+ { { 13f, 14f, 15f, 16f }, { 17f, 18f, 19f, null }, { 21f, 22f, 23f, 24f } } };
+
+ public FloatObjectArraysTest() {
+ super(floats, true, Oid.FLOAT4_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/IntArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/IntArraysTest.java
new file mode 100644
index 0000000000..01ce25a32a
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/IntArraysTest.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class IntArraysTest extends AbstractArraysTest {
+
+ private static final int[][][] ints = new int[][][] { { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } },
+ { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } };
+
+ public IntArraysTest() {
+ super(ints, true, Oid.INT4_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/IntegerObjectArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/IntegerObjectArraysTest.java
new file mode 100644
index 0000000000..026659592c
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/IntegerObjectArraysTest.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class IntegerObjectArraysTest extends AbstractArraysTest {
+
+ private static final Integer[][][] ints = new Integer[][][] {
+ { { 1, 2, 3, 4 }, { 5, null, 7, 8 }, { 9, 10, 11, 12 } },
+ { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } };
+
+ public IntegerObjectArraysTest() {
+ super(ints, true, Oid.INT4_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/LongArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/LongArraysTest.java
new file mode 100644
index 0000000000..db2c6c4428
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/LongArraysTest.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class LongArraysTest extends AbstractArraysTest {
+
+ private static final long[][][] longs = new long[][][] { { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } },
+ { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } };
+
+ public LongArraysTest() {
+ super(longs, true, Oid.INT8_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/LongObjectArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/LongObjectArraysTest.java
new file mode 100644
index 0000000000..5625fabb75
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/LongObjectArraysTest.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class LongObjectArraysTest extends AbstractArraysTest {
+
+ private static final Long[][][] longs = new Long[][][] {
+ { { 1L, 2L, null, 4L }, { 5L, 6L, 7L, 8L }, { 9L, 10L, 11L, 12L } },
+ { { 13L, 14L, 15L, 16L }, { 17L, 18L, 19L, 20L }, { 21L, 22L, 23L, 24L } } };
+
+ public LongObjectArraysTest() {
+ super(longs, true, Oid.INT8_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/PrimitiveArraySupportTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/PrimitiveArraySupportTest.java
deleted file mode 100644
index 83b028bbce..0000000000
--- a/pgjdbc/src/test/java/org/postgresql/jdbc/PrimitiveArraySupportTest.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (c) 2003, PostgreSQL Global Development Group
- * See the LICENSE file in the project root for more information.
- */
-
-package org.postgresql.jdbc;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
-import org.postgresql.core.Oid;
-
-import org.junit.Test;
-
-import java.sql.SQLFeatureNotSupportedException;
-
-public class PrimitiveArraySupportTest {
-
- public PrimitiveArraySupport longArrays = PrimitiveArraySupport.getArraySupport(new long[] {});
- public PrimitiveArraySupport intArrays = PrimitiveArraySupport.getArraySupport(new int[] {});
- public PrimitiveArraySupport shortArrays = PrimitiveArraySupport.getArraySupport(new short[] {});
- public PrimitiveArraySupport doubleArrays = PrimitiveArraySupport.getArraySupport(new double[] {});
- public PrimitiveArraySupport floatArrays = PrimitiveArraySupport.getArraySupport(new float[] {});
- public PrimitiveArraySupport booleanArrays = PrimitiveArraySupport.getArraySupport(new boolean[] {});
-
- @Test
- public void testLongBinary() throws Exception {
- final long[] longs = new long[84];
- for (int i = 0; i < 84; ++i) {
- longs[i] = i - 3;
- }
-
- final PgArray pgArray = new PgArray(null, Oid.INT8_ARRAY, longArrays.toBinaryRepresentation(null, longs));
-
- Object arrayObj = pgArray.getArray();
-
- assertThat(arrayObj, instanceOf(Long[].class));
-
- final Long[] actual = (Long[]) arrayObj;
-
- assertEquals(longs.length, actual.length);
-
- for (int i = 0; i < longs.length; ++i) {
- assertEquals(Long.valueOf(longs[i]), actual[i]);
- }
- }
-
- @Test
- public void testLongToString() throws Exception {
- final long[] longs = new long[] { 12367890987L, 987664198234L, -2982470923874L };
-
- final String arrayString = longArrays.toArrayString(',', longs);
-
- assertEquals("{12367890987,987664198234,-2982470923874}", arrayString);
-
- final String altArrayString = longArrays.toArrayString(';', longs);
-
- assertEquals("{12367890987;987664198234;-2982470923874}", altArrayString);
- }
-
- @Test
- public void testIntBinary() throws Exception {
- final int[] ints = new int[13];
- for (int i = 0; i < 13; ++i) {
- ints[i] = i - 3;
- }
-
- final PgArray pgArray = new PgArray(null, Oid.INT4_ARRAY, intArrays.toBinaryRepresentation(null, ints));
-
- Object arrayObj = pgArray.getArray();
-
- assertThat(arrayObj, instanceOf(Integer[].class));
-
- final Integer[] actual = (Integer[]) arrayObj;
-
- assertEquals(ints.length, actual.length);
-
- for (int i = 0; i < ints.length; ++i) {
- assertEquals(Integer.valueOf(ints[i]), actual[i]);
- }
- }
-
- @Test
- public void testIntToString() throws Exception {
- final int[] ints = new int[] { 12367890, 987664198, -298247092 };
-
- final String arrayString = intArrays.toArrayString(',', ints);
-
- assertEquals("{12367890,987664198,-298247092}", arrayString);
-
- final String altArrayString = intArrays.toArrayString(';', ints);
-
- assertEquals("{12367890;987664198;-298247092}", altArrayString);
-
- }
-
- @Test
- public void testShortToBinary() throws Exception {
- final short[] shorts = new short[13];
- for (int i = 0; i < 13; ++i) {
- shorts[i] = (short) (i - 3);
- }
-
- final PgArray pgArray = new PgArray(null, Oid.INT4_ARRAY, shortArrays.toBinaryRepresentation(null, shorts));
-
- Object arrayObj = pgArray.getArray();
-
- assertThat(arrayObj, instanceOf(Short[].class));
-
- final Short[] actual = (Short[]) arrayObj;
-
- assertEquals(shorts.length, actual.length);
-
- for (int i = 0; i < shorts.length; ++i) {
- assertEquals(Short.valueOf(shorts[i]), actual[i]);
- }
- }
-
- @Test
- public void testShortToString() throws Exception {
- final short[] shorts = new short[] { 123, 34, -57 };
-
- final String arrayString = shortArrays.toArrayString(',', shorts);
-
- assertEquals("{123,34,-57}", arrayString);
-
- final String altArrayString = shortArrays.toArrayString(';', shorts);
-
- assertEquals("{123;34;-57}", altArrayString);
-
- }
-
- @Test
- public void testDoubleBinary() throws Exception {
- final double[] doubles = new double[13];
- for (int i = 0; i < 13; ++i) {
- doubles[i] = i - 3.1;
- }
-
- final PgArray pgArray = new PgArray(null, Oid.FLOAT8_ARRAY, doubleArrays.toBinaryRepresentation(null, doubles));
-
- Object arrayObj = pgArray.getArray();
-
- assertThat(arrayObj, instanceOf(Double[].class));
-
- final Double[] actual = (Double[]) arrayObj;
-
- assertEquals(doubles.length, actual.length);
-
- for (int i = 0; i < doubles.length; ++i) {
- assertEquals(Double.valueOf(doubles[i]), actual[i]);
- }
- }
-
- @Test
- public void testdoubleToString() throws Exception {
- final double[] doubles = new double[] { 122353.345, 923487.235987, -23.239486 };
-
- final String arrayString = doubleArrays.toArrayString(',', doubles);
-
- assertEquals("{\"122353.345\",\"923487.235987\",\"-23.239486\"}", arrayString);
-
- final String altArrayString = doubleArrays.toArrayString(';', doubles);
-
- assertEquals("{\"122353.345\";\"923487.235987\";\"-23.239486\"}", altArrayString);
-
- }
-
- @Test
- public void testFloatBinary() throws Exception {
- final float[] floats = new float[13];
- for (int i = 0; i < 13; ++i) {
- floats[i] = (float) (i - 3.1);
- }
-
- final PgArray pgArray = new PgArray(null, Oid.FLOAT4_ARRAY, floatArrays.toBinaryRepresentation(null, floats));
-
- Object arrayObj = pgArray.getArray();
-
- assertThat(arrayObj, instanceOf(Float[].class));
-
- final Float[] actual = (Float[]) arrayObj;
-
- assertEquals(floats.length, actual.length);
-
- for (int i = 0; i < floats.length; ++i) {
- assertEquals(Float.valueOf(floats[i]), actual[i]);
- }
- }
-
- @Test
- public void testfloatToString() throws Exception {
- final float[] floats = new float[] { 122353.34f, 923487.25f, -23.2394f };
-
- final String arrayString = floatArrays.toArrayString(',', floats);
-
- assertEquals("{\"122353.34\",\"923487.25\",\"-23.2394\"}", arrayString);
-
- final String altArrayString = floatArrays.toArrayString(';', floats);
-
- assertEquals("{\"122353.34\";\"923487.25\";\"-23.2394\"}", altArrayString);
-
- }
-
- @Test
- public void testBooleanBinary() throws Exception {
- final boolean[] bools = new boolean[] { true, true, false };
-
- final PgArray pgArray = new PgArray(null, Oid.BIT, booleanArrays.toBinaryRepresentation(null, bools));
-
- Object arrayObj = pgArray.getArray();
-
- assertThat(arrayObj, instanceOf(Boolean[].class));
-
- final Boolean[] actual = (Boolean[]) arrayObj;
-
- assertEquals(bools.length, actual.length);
-
- for (int i = 0; i < bools.length; ++i) {
- assertEquals(Boolean.valueOf(bools[i]), actual[i]);
- }
- }
-
- @Test
- public void testBooleanToString() throws Exception {
- final boolean[] bools = new boolean[] { true, true, false };
-
- final String arrayString = booleanArrays.toArrayString(',', bools);
-
- assertEquals("{1,1,0}", arrayString);
-
- final String altArrayString = booleanArrays.toArrayString(';', bools);
-
- assertEquals("{1;1;0}", altArrayString);
- }
-
- @Test
- public void testStringNotSupportBinary() {
- PrimitiveArraySupport stringArrays = PrimitiveArraySupport.getArraySupport(new String[] {});
- assertNotNull(stringArrays);
- assertFalse(stringArrays.supportBinaryRepresentation());
- try {
- stringArrays.toBinaryRepresentation(null, new String[] { "1.2" });
- fail("no sql exception thrown");
- } catch (SQLFeatureNotSupportedException e) {
-
- }
- }
-}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/ShortArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/ShortArraysTest.java
new file mode 100644
index 0000000000..ed2779297f
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/ShortArraysTest.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class ShortArraysTest extends AbstractArraysTest {
+
+ private static final short[][][] shorts = new short[][][] { { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } },
+ { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } };
+
+ public ShortArraysTest() {
+ super(shorts, true, Oid.INT2_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/ShortObjectArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/ShortObjectArraysTest.java
new file mode 100644
index 0000000000..856da90475
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/ShortObjectArraysTest.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class ShortObjectArraysTest extends AbstractArraysTest {
+
+ private static final Short[][][] shorts = new Short[][][] { { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } },
+ { { 13, 14, 15, 16 }, { 17, 18, null, 20 }, { 21, 22, 23, 24 } } };
+
+ public ShortObjectArraysTest() {
+ super(shorts, true, Oid.INT2_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/StringArraysTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/StringArraysTest.java
new file mode 100644
index 0000000000..d9e131ac73
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/StringArraysTest.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+
+public class StringArraysTest extends AbstractArraysTest {
+
+ private static final String[][][] strings = new String[][][] {
+ { { "some", "String", "haVE some \u03C0", "another" }, { null, "6L", "7L", "8L" }, //unicode escape for pi character
+ { "asdf", " asdf ", "11L", null } },
+ { { "13L", null, "asasde4wtq", "16L" }, { "17L", "", "19L", "20L" }, { "21L", "22L", "23L", "24L" } } };
+
+ public StringArraysTest() {
+ super(strings, true, Oid.VARCHAR_ARRAY);
+ }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java
index b4075d6919..afcb832608 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java
@@ -11,13 +11,17 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import org.postgresql.PGProperty;
import org.postgresql.core.ServerVersion;
import org.postgresql.test.TestUtil;
+import org.postgresql.test.jdbc2.BaseTest4.BinaryMode;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
@@ -29,21 +33,44 @@
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Properties;
import java.util.Set;
/*
* TestCase to test the internal functionality of org.postgresql.jdbc2.DatabaseMetaData
*
*/
+@RunWith(Parameterized.class)
public class DatabaseMetaDataTest {
private Connection con;
+ private final BinaryMode binaryMode;
+
+ public DatabaseMetaDataTest(BinaryMode binaryMode) {
+ this.binaryMode = binaryMode;
+ }
+
+ @Parameterized.Parameters(name = "binary = {0}")
+ public static Iterable data() {
+ Collection ids = new ArrayList();
+ for (BinaryMode binaryMode : BinaryMode.values()) {
+ ids.add(new Object[]{binaryMode});
+ }
+ return ids;
+ }
@Before
public void setUp() throws Exception {
- con = TestUtil.openDB();
+ if (binaryMode == BinaryMode.FORCE) {
+ final Properties props = new Properties();
+ PGProperty.PREPARE_THRESHOLD.set(props, -1);
+ con = TestUtil.openDB(props);
+ } else {
+ con = TestUtil.openDB();
+ }
TestUtil.createTable(con, "metadatatest",
"id int4, name text, updated timestamptz, colour text, quest text");
TestUtil.dropSequence(con, "sercoltest_b_seq");
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
index 653512f820..842cd0db44 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
@@ -13,10 +13,11 @@
import org.postgresql.core.ReturningParserTest;
import org.postgresql.core.UTF8EncodingTest;
import org.postgresql.core.v3.V3ParameterListTests;
+import org.postgresql.jdbc.ArraysTest;
+import org.postgresql.jdbc.ArraysTestSuite;
import org.postgresql.jdbc.DeepBatchedInsertStatementTest;
import org.postgresql.jdbc.NoColumnMetadataIssue1613Test;
import org.postgresql.jdbc.PgSQLXMLTest;
-import org.postgresql.jdbc.PrimitiveArraySupportTest;
import org.postgresql.test.core.FixedLengthOutputStreamTest;
import org.postgresql.test.core.JavaVersionTest;
import org.postgresql.test.core.LogServerMessagePropertyTest;
@@ -42,6 +43,8 @@
@Suite.SuiteClasses({
ANTTest.class,
ArrayTest.class,
+ ArraysTest.class,
+ ArraysTestSuite.class,
BatchedInsertReWriteEnabledTest.class,
BatchExecuteTest.class,
BatchFailureTest.class,
@@ -97,7 +100,6 @@
PGTimeTest.class,
PgSQLXMLTest.class,
PreparedStatementTest.class,
- PrimitiveArraySupportTest.class,
QuotationTest.class,
ReaderInputStreamTest.class,
RefCursorTest.class,
@@ -122,7 +124,7 @@
UpdateableResultTest.class,
UpsertTest.class,
UTF8EncodingTest.class,
- V3ParameterListTests.class,
+ V3ParameterListTests.class
})
public class Jdbc2TestSuite {
}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java
index aa7ebd3b39..4803d70ed2 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java
@@ -11,6 +11,7 @@
import org.postgresql.jdbc.PreferQueryMode;
import org.postgresql.test.TestUtil;
import org.postgresql.test.jdbc2.BaseTest4;
+import org.postgresql.test.util.RegexMatcher;
import org.postgresql.util.PGobject;
import org.postgresql.util.PGtokenizer;
@@ -112,6 +113,66 @@ public void testCreateArrayOfInt() throws SQLException {
Assert.assertEquals(2, out[2].intValue());
}
+ @Test
+ public void testCreateArrayOfBytes() throws SQLException {
+
+ PreparedStatement pstmt = conn.prepareStatement("SELECT ?::bytea[]");
+ final byte[][] in = new byte[][] { { 0x01, (byte) 0xFF, (byte) 0x12 }, {}, { (byte) 0xAC, (byte) 0xE4 }, null };
+ final Array createdArray = conn.createArrayOf("bytea", in);
+
+ byte[][] inCopy = (byte[][]) createdArray.getArray();
+
+ Assert.assertEquals(4, inCopy.length);
+
+ Assert.assertArrayEquals(in[0], inCopy[0]);
+ Assert.assertArrayEquals(in[1], inCopy[1]);
+ Assert.assertArrayEquals(in[2], inCopy[2]);
+ Assert.assertArrayEquals(in[3], inCopy[3]);
+ Assert.assertNull(inCopy[3]);
+
+ pstmt.setArray(1, createdArray);
+
+ ResultSet rs = pstmt.executeQuery();
+ Assert.assertTrue(rs.next());
+ Array arr = rs.getArray(1);
+
+ byte[][] out = (byte[][]) arr.getArray();
+
+ Assert.assertEquals(4, out.length);
+
+ Assert.assertArrayEquals(in[0], out[0]);
+ Assert.assertArrayEquals(in[1], out[1]);
+ Assert.assertArrayEquals(in[2], out[2]);
+ Assert.assertArrayEquals(in[3], out[3]);
+ Assert.assertNull(out[3]);
+ }
+
+ @Test
+ public void testCreateArrayOfBytesFromString() throws SQLException {
+
+ assumeMinimumServerVersion("support for bytea[] as string requires hex string support from 9.0",
+ ServerVersion.v9_0);
+
+ PreparedStatement pstmt = conn.prepareStatement("SELECT ?::bytea[]");
+ final byte[][] in = new byte[][] { { 0x01, (byte) 0xFF, (byte) 0x12 }, {}, { (byte) 0xAC, (byte) 0xE4 }, null };
+
+ pstmt.setString(1, "{\"\\\\x01ff12\",\"\\\\x\",\"\\\\xace4\",NULL}");
+
+ ResultSet rs = pstmt.executeQuery();
+ Assert.assertTrue(rs.next());
+ Array arr = rs.getArray(1);
+
+ byte[][] out = (byte[][]) arr.getArray();
+
+ Assert.assertEquals(4, out.length);
+
+ Assert.assertArrayEquals(in[0], out[0]);
+ Assert.assertArrayEquals(in[1], out[1]);
+ Assert.assertArrayEquals(in[2], out[2]);
+ Assert.assertArrayEquals(in[3], out[3]);
+ Assert.assertNull(out[3]);
+ }
+
@Test
public void testCreateArrayOfSmallInt() throws SQLException {
PreparedStatement pstmt = conn.prepareStatement("SELECT ?::smallint[]");
@@ -332,12 +393,12 @@ public void testUUIDArray() throws SQLException {
@Test
public void testSetObjectFromJavaArray() throws SQLException {
- String[] strArray = new String[]{"a", "b", "c"};
+ String[] strArray = new String[] { "a", "b", "c" };
Object[] objCopy = Arrays.copyOf(strArray, strArray.length, Object[].class);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO arrtest(strarr) VALUES (?)");
- //cannot handle generic Object[]
+ // cannot handle generic Object[]
try {
pstmt.setObject(1, objCopy, Types.ARRAY);
pstmt.executeUpdate();
@@ -541,13 +602,16 @@ public void testToString() throws SQLException {
Array doubles = rs.getArray(1);
String actual = doubles.toString();
if (actual != null) {
+ // if a binary array is provided, the string representation looks like [0:1][0:1]={{1,2},{3,4}}
+ int idx = actual.indexOf('=');
+ if (idx > 0) {
+ actual = actual.substring(idx + 1);
+ }
// Remove all double quotes. They do not make a difference here.
actual = actual.replaceAll("\"", "");
- // Replace X.0 with just X
- actual = actual.replaceAll("\\.0+([^0-9])", "$1");
}
- Assert.assertEquals("Array.toString should use square braces",
- "{3.5,-4.5,NULL,77}", actual);
+ //the string format may vary based on how data stored
+ Assert.assertThat(actual, RegexMatcher.matchesPattern("\\{3\\.5,-4\\.5,NULL,77(.0)?\\}"));
}
} finally {
@@ -589,10 +653,34 @@ public void multiDimIntArray() throws SQLException {
rs.next();
Array resArray = rs.getArray(1);
String stringValue = resArray.toString();
+ // if a binary array is provided, the string representation looks like [0:1][0:1]={{1,2},{3,4}}
+ int idx = stringValue.indexOf('=');
+ if (idx > 0) {
+ stringValue = stringValue.substring(idx + 1);
+ }
// Both {{"1","2"},{"3","4"}} and {{1,2},{3,4}} are the same array representation
stringValue = stringValue.replaceAll("\"", "");
Assert.assertEquals("{{1,2},{3,4}}", stringValue);
TestUtil.closeQuietly(rs);
TestUtil.closeQuietly(ps);
}
+
+ @Test
+ public void insertAndQueryMultiDimArray() throws SQLException {
+ Array arr = con.createArrayOf("int4", new int[][] { { 1, 2 }, { 3, 4 } });
+ PreparedStatement insertPs = con.prepareStatement("INSERT INTO arrtest(intarr2) VALUES (?)");
+ insertPs.setArray(1, arr);
+ insertPs.execute();
+ insertPs.close();
+
+ PreparedStatement selectPs = con.prepareStatement("SELECT intarr2 FROM arrtest");
+ ResultSet rs = selectPs.executeQuery();
+ rs.next();
+
+ Array array = rs.getArray(1);
+ Integer[][] secondRowValues = (Integer[][]) array.getArray(2, 1);
+
+ Assert.assertEquals(3, secondRowValues[0][0].intValue());
+ Assert.assertEquals(4, secondRowValues[0][1].intValue());
+ }
}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/util/RegexMatcher.java b/pgjdbc/src/test/java/org/postgresql/test/util/RegexMatcher.java
new file mode 100644
index 0000000000..382eb4cb2d
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/test/util/RegexMatcher.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.util;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.util.regex.Pattern;
+
+/**
+ * Provides a matcher for String objects which does a regex comparison.
+ */
+public final class RegexMatcher extends TypeSafeMatcher {
+
+ private final Pattern pattern;
+
+ /**
+ * @param pattern
+ * The pattern to match items on.
+ */
+ private RegexMatcher(Pattern pattern) {
+ this.pattern = pattern;
+ }
+
+ public static Matcher matchesPattern(String pattern) {
+ return new RegexMatcher(Pattern.compile(pattern));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("matches regex=" + pattern.toString());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean matchesSafely(String item) {
+ return pattern.matcher(item).matches();
+ }
+
+}