Hello Kris and others,
in the attachment you can find rewriten code for AbstractJdbc2Array - it
contains working implementations of both getArray and getResultSet
methods. I have made all required changes comparing to my previous code,
which means that user can set "compatible" parameter to < 8.2 in order
to use primitive types and not to parse NULL as Java nulls.
Please test the code and if everything is OK please add it to CSV (or
maybe I could do it by myself, then please point me how do log-in etc...).
If you find any problems or have questions please write me an email.
PS. Sorry for the delay sending the code, but I was on vacation :-)
Best regards,
Marek Lewczuk
/*-------------------------------------------------------------------------
*
* Copyright (c) 2004-2005, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Array.java,v 1.18 2005/12/04 21:40:33 jurka Exp $
*
*-------------------------------------------------------------------------
*/
package org.postgresql.jdbc2;
import org.postgresql.core.*;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.postgresql.util.GT;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Map;
import java.util.Vector;
/**
* Array is used collect one column of query result data.
*
* <p>Read a field of type Array into either a natively-typed
* Java array object or a ResultSet. Accessor methods provide
* the ability to capture array slices.
*
* <p>Other than the constructor all methods are direct implementations
* of those specified for java.sql.Array. Please refer to the javadoc
* for java.sql.Array for detailed descriptions of the functionality
* and parameters of the methods of this class.
*
* @see ResultSet#getArray
*/
public class AbstractJdbc2Array {
/**
* Array list implementation specific for storing PG array elements.
*/
private static class PgArrayList extends ArrayList {
private static final long serialVersionUID = 2052783752654562677L;
/**
* Whether multi-dimensional array.
*/
boolean isMultiDimensional = false;
}
/**
* A database connection.
*/
private BaseConnection connection = null;
/**
* The ResultSet from which to get the data for this Array.
*/
private BaseResultSet resultSet;
/**
* The Field descriptor for the field to load into this Array.
*/
private Field field = null;
/**
* 1-based index of the query field to load into this Array.
*/
private int fieldIndex = 0;
/**
* Field value as String.
*/
private String fieldString = null;
/**
* Whether Object[] should be used instead primitive arrays. Object[] can
* contain null elements. It should be set to <Code>true</Code> if
* {@link BaseConnection#haveMinimumCompatibleVersion(String)} returns
* <Code>true</Code> for argument "8.2".
*/
private final boolean useObjects;
/**
* Create a new Array.
*
* @param connection a database connection
* @param index 1-based index of the query field to load into this Array
* @param field the Field descriptor for the field to load into this Array
* @param result the ResultSet from which to get the data for this Array
*/
public AbstractJdbc2Array (BaseConnection connection, int index, Field field, BaseResultSet result) throws
SQLException{
this.connection = connection;
this.field = field;
this.resultSet = result;
this.fieldIndex = index;
this.fieldString = result.getFixedString(index);
this.useObjects = connection.haveMinimumCompatibleVersion("8.2");
}
public Object getArray() throws SQLException {
return getArrayImpl(1, 0, null);
}
public Object getArray(long index, int count) throws SQLException {
return getArrayImpl(index, count, null);
}
public Object getArrayImpl(Map map) throws SQLException {
return getArrayImpl(1, 0, map);
}
public Object getArrayImpl (long index, int count, Map map) throws SQLException {
// for now maps aren't supported.
if (map != null && !map.isEmpty()) throw org.postgresql.Driver.notImplemented(this.getClass(),
"getArrayImpl(long,int,Map)");
// array index is out of range
if (index < 1) throw new PSQLException(GT.tr("The array index is out of range: {0}", new Long(index)),
PSQLState.DATA_ERROR);
PgArrayList array = buildArrayList(fieldString);
if (count == 0) count = array.size();
// array index out of range
if ((--index) + count > array.size()) throw new PSQLException(GT.tr("The array index is out of range: {0},
numberof elements: {1}.", new Object[]{new Long(index + count), new Long(array.size())}), PSQLState.DATA_ERROR);
return buildArray(array, (int) index, count);
}
/**
* Build {@link ArrayList} from String input.
*
* @param String representation of an array
*/
private PgArrayList buildArrayList (String list) {
PgArrayList array = new PgArrayList();
if (list != null) {
char[] chars = list.toCharArray();
StringBuffer buffer = new StringBuffer();
boolean insideString = false;
boolean wasInsideString = false; // needed for checking if NULL value occured
Vector dims = new Vector(); // array dimension arrays
PgArrayList curArray = array; // 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++;
// subarray start
else if (!insideString && chars[i] == '{' ) {
if (dims.size() == 0) dims.add(array);
else {
PgArrayList a = new PgArrayList();
PgArrayList p = ((PgArrayList) dims.lastElement());
{
p.add(a);
p.isMultiDimensional = true;
}
dims.add(a);
}
curArray = (PgArrayList) dims.lastElement();
buffer = new StringBuffer();
continue;
}
// quoted element
else if (chars[i] == '"') {
insideString = !insideString;
wasInsideString = true;
continue;
}
// array end
else if (!insideString && (chars[i] == ',' || chars[i] == '}') || i == chars.length - 1) {
if ( chars[i] != '"' && chars[i] != '}' && chars[i] != ',' && buffer != null)
buffer.append(chars[i]);
String b = buffer == null ? null : buffer.toString();
if (b != null) curArray.add(!useObjects && !wasInsideString && b.equals("NULL") ? null : b);
wasInsideString = false;
buffer = new StringBuffer();
if (chars[i] == '}') {
dims.remove(dims.size() - 1);
if (dims.size() > 0) curArray = (PgArrayList) dims.lastElement();
buffer = null;
}
continue;
}
if (buffer != null) buffer.append( chars[i] );
}
}
return array;
}
/**
* Convert {@link ArrayList} to array.
*
* @param input list to be converted into array
*/
private Object buildArray (PgArrayList input, int index, int count) throws SQLException {
if (count == -1) count = input.size();
// array to be returned
Object ret = null;
// whether multi-dimensional array
boolean multi = input.isMultiDimensional;
// array elements counter
int length = 0;
// array type
final int type = getBaseType();
if (type == Types.BIT) {
boolean[] pa = null;
Object[] oa = null;
if (multi || useObjects) ret = oa = (multi ? new Object[count] : new Boolean[count]);
else ret = pa = new boolean[count];
for ( ; count > 0; count--) {
Object o = input.get(index++);
if (multi || useObjects) oa[length++] = o == null ? null : (multi ? buildArray((PgArrayList) o, 0, -1)
:new Boolean(AbstractJdbc2ResultSet.toBoolean((String) o)));
else pa[length++] = o == null ? false : AbstractJdbc2ResultSet.toBoolean((String) o);
}
}
else if (type == Types.SMALLINT || type == Types.INTEGER) {
int[] pa = null;
Object[] oa = null;
if (multi || useObjects) ret = oa = (multi ? new Object[count] : new Integer[count]);
else ret = pa = new int[count];
for ( ; count > 0; count--) {
Object o = input.get(index++);
if (multi || useObjects) oa[length++] = o == null ? null : (multi ? buildArray((PgArrayList) o, 0, -1)
:new Integer(AbstractJdbc2ResultSet.toInt((String) o)));
else pa[length++] = o == null ? 0 : AbstractJdbc2ResultSet.toInt((String) o);
}
}
else if (type == Types.BIGINT) {
long[] pa = null;
Object[] oa = null;
if (multi || useObjects) ret = oa = (multi ? new Object[count] : new Long[count]);
else ret = pa = new long[count];
for ( ; count > 0; count--) {
Object o = input.get(index++);
boolean p = false;
if (multi || useObjects) oa[length++] = o == null ? null : (multi ? buildArray((PgArrayList) o, 0, -1)
:new Long(AbstractJdbc2ResultSet.toLong((String) o)));
else pa[length++] = o == null ? 0l : AbstractJdbc2ResultSet.toLong((String) o);
}
}
else if (type == Types.NUMERIC) {
Object[] oa = null;
ret = oa = (multi ? new Object[count] : new BigDecimal[count]);
for ( ; count > 0; count--) {
Object v = input.get(index++);
oa[length++] = multi && v != null ? buildArray((PgArrayList) v, 0, -1) : (v == null ? null :
AbstractJdbc2ResultSet.toBigDecimal((String)v, -1));
}
}
else if (type == Types.REAL) {
float[] pa = null;
Object[] oa = null;
if (multi || useObjects) ret = oa = (multi ? new Object[count] : new Float[count]);
else ret = pa = new float[count];
for ( ; count > 0; count--) {
Object o = input.get(index++);
boolean p = false;
if (multi || useObjects) oa[length++] = o == null ? null : (multi ? buildArray((PgArrayList) o, 0, -1)
:new Float(AbstractJdbc2ResultSet.toFloat((String) o)));
else pa[length++] = o == null ? 0f : AbstractJdbc2ResultSet.toFloat((String) o);
}
}
else if (type == Types.DOUBLE) {
double[] pa = null;
Object[] oa = null;
if (multi || useObjects) ret = oa = (multi ? new Object[count] : new Double[count]);
else ret = pa = new double[count];
for ( ; count > 0; count--) {
Object o = input.get(index++);
boolean p = false;
if (multi || useObjects) oa[length++] = o == null ? null : (multi ? buildArray((PgArrayList) o, 0, -1)
:new Double(AbstractJdbc2ResultSet.toDouble((String) o)));
else pa[length++] = o == null ? 0d : AbstractJdbc2ResultSet.toDouble((String) o);
}
}
else if (type == Types.CHAR || type == Types.VARCHAR) {
Object[] oa = null;
ret = oa = (multi ? new Object[count] : new String[count]);
for ( ; count > 0; count--) {
Object v = input.get(index++);
oa[length++] = multi && v != null ? buildArray((PgArrayList) v, 0, -1) : v;
}
}
else if (type == Types.DATE) {
Object[] oa = null;
ret = oa = (multi ? new Object[count] : new java.sql.Date[count]);
for ( ; count > 0; count--) {
Object v = input.get(index++);
oa[length++] = multi && v != null ? buildArray((PgArrayList) v, 0, -1) : (v == null ? null :
connection.getTimestampUtils().toDate(null,(String) v));
}
}
else if (type == Types.TIME) {
Object[] oa = null;
ret = oa = (multi ? new Object[count] : new java.sql.Time[count]);
for ( ; count > 0; count--) {
Object v = input.get(index++);
oa[length++] = multi && v != null ? buildArray((PgArrayList) v, 0, -1) : (v == null ? null :
connection.getTimestampUtils().toTime(null,(String) v));
}
}
else if (type == Types.TIMESTAMP) {
Object[] oa = null;
ret = oa = (multi ? new Object[count] : new java.sql.Timestamp[count]);
for ( ; count > 0; count--) {
Object v = input.get(index++);
oa[length++] = multi && v != null ? buildArray((PgArrayList) v, 0, -1) : (v == null ? null :
connection.getTimestampUtils().toTimestamp(null,(String) v));
}
}
// other datatypes not currently supported
else {
if (connection.getLogger().logDebug()) connection.getLogger().debug("getArrayImpl(long,int,Map) with " +
getBaseTypeName());
throw org.postgresql.Driver.notImplemented(this.getClass(), "getArrayImpl(long,int,Map)");
}
return ret;
}
public int getBaseType () throws SQLException {
return connection.getSQLType(getBaseTypeName());
}
public String getBaseTypeName () throws SQLException {
String t = connection.getPGType(field.getOID());
if (t.charAt(0) == '_') t = t.substring(1);
return t;
}
public java.sql.ResultSet getResultSet () throws SQLException {
return getResultSetImpl(1, 0, null);
}
public java.sql.ResultSet getResultSet (long index, int count) throws SQLException {
return getResultSetImpl(index, count, null);
}
public java.sql.ResultSet getResultSetImpl (Map map) throws SQLException {
return getResultSetImpl(1, 0, map);
}
public ResultSet getResultSetImpl (long index, int count, Map map) throws SQLException {
// for now maps aren't supported.
if (map != null && !map.isEmpty()) throw org.postgresql.Driver.notImplemented(this.getClass(),
"getResultSetImpl(long,int,Map)");
// array index is out of range
if (index < 1) throw new PSQLException(GT.tr("The array index is out of range: {0}", new Long(index)),
PSQLState.DATA_ERROR);
PgArrayList list = buildArrayList(fieldString);
if (count == 0) count = list.size();
// array index out of range
if ((--index) + count > list.size()) throw new PSQLException(GT.tr("The array index is out of range: {0},
numberof elements: {1}.", new Object[]{new Long(index + count), new Long(list.size())}), PSQLState.DATA_ERROR);
Vector rows = new Vector();
// array type
final int type = getBaseType();
Field[] fields = new Field[2];
{
fields[0] = new Field("INDEX", Oid.INT2);
if (type == Types.BIT) fields[1] = new Field("VALUE", Oid.BOOL);
else if (type == Types.SMALLINT) fields[1] = new Field("VALUE", Oid.INT2);
else if (type == Types.INTEGER) fields[1] = new Field("VALUE", Oid.INT4);
else if (type == Types.BIGINT) fields[1] = new Field("VALUE", Oid.INT8);
else if (type == Types.NUMERIC) fields[1] = new Field("VALUE", Oid.NUMERIC);
else if (type == Types.REAL) fields[1] = new Field("VALUE", Oid.FLOAT4);
else if (type == Types.DOUBLE) fields[1] = new Field("VALUE", Oid.FLOAT8);
else if (type == Types.CHAR) fields[1] = new Field("VALUE", Oid.BPCHAR);
else if (type == Types.VARCHAR) fields[1] = new Field("VALUE", Oid.VARCHAR);
else if (type == Types.DATE) fields[1] = new Field("VALUE", Oid.DATE);
else if (type == Types.TIME) fields[1] = new Field("VALUE", Oid.TIME);
else if (type == Types.TIMESTAMP) fields[1] = new Field("VALUE", Oid.TIMESTAMPTZ);
// other data types not currently supported
else {
if (connection.getLogger().logDebug()) connection.getLogger().debug("getResultSetImpl(long,int,Map)
with" + getBaseTypeName());
throw org.postgresql.Driver.notImplemented(this.getClass(), "getResultSetImpl(long,int,Map)");
}
}
if (list.isMultiDimensional) {
for (int i = 0; i < list.size(); i++) {
byte[][] t = new byte[2][0];
Object v = list.get(i);
t[0] = connection.encodeString(Integer.toString(i + 1));
t[1] = connection.encodeString(v == null ? "NULL" : toString((PgArrayList) v));
rows.add(t);
}
}
else {
for (int i = 0; i < list.size(); i++) {
byte[][] t = new byte[2][0];
String v = (String) list.get(i);
t[0] = connection.encodeString(Integer.toString(i + 1));
t[1] = connection.encodeString(v == null ? "NULL" : v);
rows.add(t);
}
}
BaseStatement stat = (BaseStatement) connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
return (ResultSet) stat.createDriverResultSet(fields, rows);
}
public String toString () {
return fieldString;
}
/**
* Convert array list to PG String representation (e.g. {0,1,2}).
*/
private String toString (PgArrayList list) throws SQLException {
StringBuffer b = new StringBuffer();
b.append('{');
for (int i = 0; i < list.size(); i++) {
Object v = list.get(i);
if (i > 0) b.append(',');
if (v == null) b.append("NULL");
else if (v instanceof PgArrayList) b.append(toString((PgArrayList) v));
else b.append('"').append(connection.escapeString((String) v)).append('"');
}
b.append('}');
return b.toString();
}
}