/*------------------------------------------------------------------------- * * 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. * *

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. * *

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 true if * {@link BaseConnection#haveMinimumCompatibleVersion(String)} returns * true 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}, number of 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}, number of 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(); } }