Permalink
Browse files

feat: primitive arrays (#887)

Support mapping of Java types short[], int[], long[], float[], double[], boolean[], String[] to relevant types in PostgreSQL via org.postgresql.PGConnection.createArrayOf(String, Object) API.

closes #871
  • Loading branch information...
bokken authored and vlsi committed Dec 19, 2017
1 parent ed27c5b commit 3e0491ac3833800721b98e7437635cf6ab338162
@@ -1,5 +1,8 @@
name: PostgreSQL JDBC Driver website
markdown: redcarpet
redcarpet:
extensions:
- tables
highlighter: pygments
excerpt_separator: <!--more-->
exclude:
@@ -0,0 +1,29 @@
---
layout: default_docs
title: Arrays
header: Chapter 9. PostgreSQL™ Extensions to the JDBC API
resource: media
previoustitle: Physical and Logical replication API
previous: replication.html
nexttitle: Chapter 10. Using the Driver in a Multithreaded or a Servlet Environment
next: thread.html
---

PostgreSQL™ provides robust support for array data types as column types, function arguments
and criteria in where clauses. There are several ways to create arrays with pgjdbc.

The [java.sql.Connection.createArrayOf(String, Object\[\])](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#createArrayOf-java.lang.String-java.lang.Object:A-) can be used to create an [java.sql.Array](https://docs.oracle.com/javase/8/docs/api/java/sql/Array.html) from `Object[]` instances (Note: this includes both primitive and object multi-dimensional arrays).
A similar method `org.postgresql.PGConnection.createArrayOf(String, Object)` provides support for primitive array types.
The `java.sql.Array` object returned from these methods can be used in other methods, such as [PreparedStatement.setArray(int, Array)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setArray-int-java.sql.Array-).

Additionally, the following types of arrays can be used in `PreparedStatement.setObject` methods and will use the defined type mapping:

Java Type | Default PostgreSQL™ Type
--- | ---
`short[]` | `int2[]`
`int[]` | `int4[]`
`long[]` | `int8[]`
`float[]` | `float4[]`
`double[]` | `float8[]`
`boolean[]` | `bool[]`
`String[]` | `varchar[]`
@@ -17,6 +17,7 @@ next: geometric.html
* [Listen / Notify](listennotify.html)
* [Server Prepared Statements](server-prepare.html)
* [Physical and Logical replication API](replication.html)
* [Arrays](arrays.html)

PostgreSQL™ is an extensible database system. You can add your own functions to
the server, which can then be called from queries, or even add your own data types.
@@ -120,6 +120,7 @@ <h3 class="c2">Table of Contents</h3>
<dt><a href="listennotify.html">Listen / Notify</a></dt>
<dt><a href="server-prepare.html">Server Prepared Statements</a></dt>
<dt><a href="replication.html">Physical and Logical replication API</a></dt>
<dt><a href="arrays.html">Arrays</a></dt>
</dl>
</dd>

@@ -5,8 +5,8 @@ header: Chapter 9. PostgreSQL™ Extensions to the JDBC API
resource: media
previoustitle: Server Prepared Statements
previous: server-prepare.html
nexttitle: Chapter 10. Using the Driver in a Multithreaded or a Servlet Environment
next: thread.html
nexttitle: Arrays
next: arrays.html
---

**Table of Contents**
@@ -13,6 +13,7 @@
import org.postgresql.replication.PGReplicationConnection;
import org.postgresql.util.PGobject;

import java.sql.Array;
import java.sql.SQLException;
import java.sql.Statement;

@@ -21,6 +22,25 @@
* returned by the PostgreSQL driver implement PGConnection.
*/
public interface PGConnection {

/**
* Creates an {@link Array} wrapping <i>elements</i>. This is similar to
* {@link java.sql.Connection#createArrayOf(String, Object[])}, but also
* provides support for primitive arrays.
*
* @param typeName
* The SQL name of the type to map the <i>elements</i> to.
* Must not be {@code null}.
* @param elements
* The array of objects to map. A {@code null} value will result in
* an {@link Array} representing {@code null}.
* @return An {@link Array} wrapping <i>elements</i>.
* @throws SQLException
* If for some reason the array cannot be created.
* @see java.sql.Connection#createArrayOf(String, Object[])
*/
Array createArrayOf(String typeName, Object elements) throws SQLException;

/**
* This method returns any notifications that have been received since the last call to this
* method. Returns null if there have been no notifications.
@@ -250,6 +250,9 @@ private int storeValues(final Object[] arr, int elementOid, final int[] dims, in
Encoding encoding = connection.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) {
@@ -392,6 +395,8 @@ private int calcRemainingDataLength(int[] dims, int pos, int elementOid, int thi
case Oid.TEXT:
case Oid.VARCHAR:
return String.class;
case Oid.BOOL:
return Boolean.class;
default:
ArrayAssistant arrElemBuilder = ArrayAssistantRegistry.getAssistant(oid);
if (arrElemBuilder != null) {
@@ -900,11 +905,17 @@ public ResultSet getResultSetImpl(long index, int count, Map<String, Class<?>> m
public String toString() {
if (fieldString == null && fieldBytes != null) {
try {
Object array = readBinaryArray(1,0);
java.sql.Array tmpArray = connection.createArrayOf(getBaseTypeName(), (Object[]) array);
fieldString = tmpArray.toString();
Object array = readBinaryArray(1, 0);

final PrimitiveArraySupport arraySupport = PrimitiveArraySupport.getArraySupport(array);
if (arraySupport != null) {
fieldString = arraySupport.toArrayString(connection.getTypeInfo().getArrayDelimiter(oid), array);
} else {
java.sql.Array tmpArray = connection.createArrayOf(getBaseTypeName(), (Object[]) array);
fieldString = tmpArray.toString();
}
} catch (SQLException e) {
fieldString = "NULL"; //punt
fieldString = "NULL"; // punt
}
}
return fieldString;
@@ -1156,7 +1156,12 @@ private static void appendArray(StringBuilder sb, Object elements, char delim) {
if (o == null) {
sb.append("NULL");
} else if (o.getClass().isArray()) {
appendArray(sb, o, delim);
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);
@@ -1271,6 +1276,49 @@ public Struct createStruct(String typeName, Object[] attributes) throws SQLExcep
throw org.postgresql.Driver.notImplemented(this.getClass(), "createStruct(String, Object[])");
}

@Override
public Array createArrayOf(String typeName, Object elements) throws SQLException {
checkClosed();

final TypeInfo typeInfo = getTypeInfo();

final int oid = typeInfo.getPGArrayType(typeName);
final char delim = typeInfo.getArrayDelimiter(oid);

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);
}

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();
}

return makeArray(oid, arrayString);
}

@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
checkClosed();
@@ -1283,6 +1331,10 @@ public Array createArrayOf(String typeName, Object[] elements) throws SQLExcepti
PSQLState.INVALID_NAME);
}

if (elements == null) {
return makeArray(oid, null);
}

char delim = getTypeInfo().getArrayDelimiter(oid);
StringBuilder sb = new StringBuilder();
appendArray(sb, elements, delim);
@@ -13,6 +13,7 @@
import org.postgresql.core.Query;
import org.postgresql.core.QueryExecutor;
import org.postgresql.core.ServerVersion;
import org.postgresql.core.TypeInfo;
import org.postgresql.core.v3.BatchedQuery;
import org.postgresql.largeobject.LargeObject;
import org.postgresql.largeobject.LargeObjectManager;
@@ -656,6 +657,8 @@ public void setObject(int parameterIndex, Object in, int targetSqlType, int scal
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}",
@@ -681,6 +684,21 @@ public void setObject(int parameterIndex, Object in, int targetSqlType, int scal
}
}

private <A> void setPrimitiveArray(int parameterIndex, A in) throws SQLException {
final PrimitiveArraySupport<A> arrayToString = PrimitiveArraySupport.getArraySupport(in);

final TypeInfo typeInfo = connection.getTypeInfo();

final int oid = arrayToString.getDefaultArrayTypeOid(typeInfo);

if (arrayToString.supportBinaryRepresentation() && connection.getPreferQueryMode() != PreferQueryMode.SIMPLE) {
bindBytes(parameterIndex, arrayToString.toBinaryRepresentation(connection, in), oid);
} else {
final char delim = typeInfo.getArrayDelimiter(oid);
setString(parameterIndex, arrayToString.toArrayString(delim, in), oid);
}
}

private static String asString(final Clob in) throws SQLException {
return in.getSubString(1, (int) in.length());
}
@@ -942,6 +960,8 @@ public void setObject(int parameterIndex, Object x) throws SQLException {
setMap(parameterIndex, (Map<?, ?>) x);
} else if (x instanceof Number) {
setNumber(parameterIndex, (Number) x);
} else if (PrimitiveArraySupport.isSupportedPrimitiveArray(x)) {
setPrimitiveArray(parameterIndex, x);
} else {
// Can't infer a type.
throw new PSQLException(GT.tr(
Oops, something went wrong.

0 comments on commit 3e0491a

Please sign in to comment.