Skip to content

Commit

Permalink
feat: primitive arrays (#887)
Browse files Browse the repository at this point in the history
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 3e0491a
Show file tree
Hide file tree
Showing 15 changed files with 1,067 additions and 14 deletions.
3 changes: 3 additions & 0 deletions docs/_config.yml
@@ -1,5 +1,8 @@
name: PostgreSQL JDBC Driver website name: PostgreSQL JDBC Driver website
markdown: redcarpet markdown: redcarpet
redcarpet:
extensions:
- tables
highlighter: pygments highlighter: pygments
excerpt_separator: <!--more--> excerpt_separator: <!--more-->
exclude: exclude:
Expand Down
29 changes: 29 additions & 0 deletions docs/documentation/head/arrays.md
@@ -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[]`
1 change: 1 addition & 0 deletions docs/documentation/head/ext.md
Expand Up @@ -17,6 +17,7 @@ next: geometric.html
* [Listen / Notify](listennotify.html) * [Listen / Notify](listennotify.html)
* [Server Prepared Statements](server-prepare.html) * [Server Prepared Statements](server-prepare.html)
* [Physical and Logical replication API](replication.html) * [Physical and Logical replication API](replication.html)
* [Arrays](arrays.html)


PostgreSQL™ is an extensible database system. You can add your own functions to 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. the server, which can then be called from queries, or even add your own data types.
Expand Down
1 change: 1 addition & 0 deletions docs/documentation/head/index.html
Expand Up @@ -120,6 +120,7 @@ <h3 class="c2">Table of Contents</h3>
<dt><a href="listennotify.html">Listen / Notify</a></dt> <dt><a href="listennotify.html">Listen / Notify</a></dt>
<dt><a href="server-prepare.html">Server Prepared Statements</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="replication.html">Physical and Logical replication API</a></dt>
<dt><a href="arrays.html">Arrays</a></dt>
</dl> </dl>
</dd> </dd>


Expand Down
4 changes: 2 additions & 2 deletions docs/documentation/head/replication.md
Expand Up @@ -5,8 +5,8 @@ header: Chapter 9. PostgreSQL™ Extensions to the JDBC API
resource: media resource: media
previoustitle: Server Prepared Statements previoustitle: Server Prepared Statements
previous: server-prepare.html previous: server-prepare.html
nexttitle: Chapter 10. Using the Driver in a Multithreaded or a Servlet Environment nexttitle: Arrays
next: thread.html next: arrays.html
--- ---


**Table of Contents** **Table of Contents**
Expand Down
20 changes: 20 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/PGConnection.java
Expand Up @@ -13,6 +13,7 @@
import org.postgresql.replication.PGReplicationConnection; import org.postgresql.replication.PGReplicationConnection;
import org.postgresql.util.PGobject; import org.postgresql.util.PGobject;


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


Expand All @@ -21,6 +22,25 @@
* returned by the PostgreSQL driver implement PGConnection. * returned by the PostgreSQL driver implement PGConnection.
*/ */
public interface 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 * This method returns any notifications that have been received since the last call to this
* method. Returns null if there have been no notifications. * method. Returns null if there have been no notifications.
Expand Down
19 changes: 15 additions & 4 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
Expand Up @@ -250,6 +250,9 @@ private int storeValues(final Object[] arr, int elementOid, final int[] dims, in
Encoding encoding = connection.getEncoding(); Encoding encoding = connection.getEncoding();
arr[i] = encoding.decode(fieldBytes, pos, len); arr[i] = encoding.decode(fieldBytes, pos, len);
break; break;
case Oid.BOOL:
arr[i] = ByteConverter.bool(fieldBytes, pos);
break;
default: default:
ArrayAssistant arrAssistant = ArrayAssistantRegistry.getAssistant(elementOid); ArrayAssistant arrAssistant = ArrayAssistantRegistry.getAssistant(elementOid);
if (arrAssistant != null) { if (arrAssistant != null) {
Expand Down Expand Up @@ -392,6 +395,8 @@ private Class<?> elementOidToClass(int oid) throws SQLException {
case Oid.TEXT: case Oid.TEXT:
case Oid.VARCHAR: case Oid.VARCHAR:
return String.class; return String.class;
case Oid.BOOL:
return Boolean.class;
default: default:
ArrayAssistant arrElemBuilder = ArrayAssistantRegistry.getAssistant(oid); ArrayAssistant arrElemBuilder = ArrayAssistantRegistry.getAssistant(oid);
if (arrElemBuilder != null) { if (arrElemBuilder != null) {
Expand Down Expand Up @@ -900,11 +905,17 @@ public ResultSet getResultSetImpl(long index, int count, Map<String, Class<?>> m
public String toString() { public String toString() {
if (fieldString == null && fieldBytes != null) { if (fieldString == null && fieldBytes != null) {
try { try {
Object array = readBinaryArray(1,0); Object array = readBinaryArray(1, 0);
java.sql.Array tmpArray = connection.createArrayOf(getBaseTypeName(), (Object[]) array);
fieldString = tmpArray.toString(); 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) { } catch (SQLException e) {
fieldString = "NULL"; //punt fieldString = "NULL"; // punt
} }
} }
return fieldString; return fieldString;
Expand Down
54 changes: 53 additions & 1 deletion pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
Expand Up @@ -1156,7 +1156,12 @@ private static void appendArray(StringBuilder sb, Object elements, char delim) {
if (o == null) { if (o == null) {
sb.append("NULL"); sb.append("NULL");
} else if (o.getClass().isArray()) { } 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 { } else {
String s = o.toString(); String s = o.toString();
PgArray.escapeArrayElement(sb, s); PgArray.escapeArrayElement(sb, s);
Expand Down Expand Up @@ -1271,6 +1276,49 @@ public Struct createStruct(String typeName, Object[] attributes) throws SQLExcep
throw org.postgresql.Driver.notImplemented(this.getClass(), "createStruct(String, Object[])"); 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 @Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException { public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
checkClosed(); checkClosed();
Expand All @@ -1283,6 +1331,10 @@ public Array createArrayOf(String typeName, Object[] elements) throws SQLExcepti
PSQLState.INVALID_NAME); PSQLState.INVALID_NAME);
} }


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

char delim = getTypeInfo().getArrayDelimiter(oid); char delim = getTypeInfo().getArrayDelimiter(oid);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
appendArray(sb, elements, delim); appendArray(sb, elements, delim);
Expand Down
20 changes: 20 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
Expand Up @@ -13,6 +13,7 @@
import org.postgresql.core.Query; import org.postgresql.core.Query;
import org.postgresql.core.QueryExecutor; import org.postgresql.core.QueryExecutor;
import org.postgresql.core.ServerVersion; import org.postgresql.core.ServerVersion;
import org.postgresql.core.TypeInfo;
import org.postgresql.core.v3.BatchedQuery; import org.postgresql.core.v3.BatchedQuery;
import org.postgresql.largeobject.LargeObject; import org.postgresql.largeobject.LargeObject;
import org.postgresql.largeobject.LargeObjectManager; import org.postgresql.largeobject.LargeObjectManager;
Expand Down Expand Up @@ -656,6 +657,8 @@ public void setObject(int parameterIndex, Object in, int targetSqlType, int scal
case Types.ARRAY: case Types.ARRAY:
if (in instanceof Array) { if (in instanceof Array) {
setArray(parameterIndex, (Array) in); setArray(parameterIndex, (Array) in);
} else if (PrimitiveArraySupport.isSupportedPrimitiveArray(in)) {
setPrimitiveArray(parameterIndex, in);
} else { } else {
throw new PSQLException( throw new PSQLException(
GT.tr("Cannot cast an instance of {0} to type {1}", GT.tr("Cannot cast an instance of {0} to type {1}",
Expand All @@ -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 { private static String asString(final Clob in) throws SQLException {
return in.getSubString(1, (int) in.length()); return in.getSubString(1, (int) in.length());
} }
Expand Down Expand Up @@ -942,6 +960,8 @@ public void setObject(int parameterIndex, Object x) throws SQLException {
setMap(parameterIndex, (Map<?, ?>) x); setMap(parameterIndex, (Map<?, ?>) x);
} else if (x instanceof Number) { } else if (x instanceof Number) {
setNumber(parameterIndex, (Number) x); setNumber(parameterIndex, (Number) x);
} else if (PrimitiveArraySupport.isSupportedPrimitiveArray(x)) {
setPrimitiveArray(parameterIndex, x);
} else { } else {
// Can't infer a type. // Can't infer a type.
throw new PSQLException(GT.tr( throw new PSQLException(GT.tr(
Expand Down

0 comments on commit 3e0491a

Please sign in to comment.