/*------------------------------------------------------------------------- * * 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.Timestamp; import java.sql.Types; import java.util.ArrayList; import java.util.Map; import java.util.Vector; import java.util.GregorianCalendar; /** * 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 { private BaseConnection conn = null; private Field field = null; private BaseResultSet rs; private int idx = 0; private String rawString = null; /** * Create a new Array * * @param conn a database connection * @param idx 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 rs the ResultSet from which to get the data for this Array */ public AbstractJdbc2Array(BaseConnection conn, int idx, Field field, BaseResultSet rs ) throws SQLException { this.conn = conn; this.field = field; this.rs = rs; this.idx = idx; this.rawString = rs.getFixedString(idx); } 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 { if ( map != null && !map.isEmpty()) // For now maps aren't supported. throw org.postgresql.Driver.notImplemented(this.getClass(), "getArrayImpl(long,int,Map)"); if (index < 1) throw new PSQLException(GT.tr("The array index is out of range: {0}", new Long(index)), PSQLState.DATA_ERROR); Object retVal = null; ArrayList array = new ArrayList(); /* Check if the String is also not an empty array * otherwise there will be an exception thrown below * in the ResultSet.toX with an empty string. * -- Doug Fields Feb 20, 2002 */ if ( rawString != null && !rawString.equals("{}") ) { char[] chars = rawString.toCharArray(); StringBuffer sbuf = new StringBuffer(); boolean insideString = false; boolean wasInsideString = false; // needed for checking if NULL value occured Vector dims = new Vector(); // array dimension arrays ArrayList 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++ ) { if ( chars[i] == '\\' ) //escape character that we need to skip i++; else if (!insideString && chars[i] == '{' ) { if (dims.size() == 0) dims.add(array); else { ArrayList a = new ArrayList(); ((ArrayList) dims.lastElement()).add(a); dims.add(a); } curArray = (ArrayList) dims.lastElement(); sbuf = new StringBuffer(); continue; } else if (chars[i] == '"') { insideString = !insideString; wasInsideString = true; continue; } else if (!insideString && (chars[i] == ',' || chars[i] == '}') || i == chars.length - 1) { if ( chars[i] != '"' && chars[i] != '}' && chars[i] != ',' && sbuf != null) sbuf.append(chars[i]); String b = sbuf == null ? null : sbuf.toString(); if (b != null) curArray.add(wasInsideString == false && b.equals("NULL") ? null : b); wasInsideString = false; sbuf = new StringBuffer(); if (chars[i] == '}') { dims.remove(dims.size() - 1); if (dims.size() > 0) curArray = (ArrayList) dims.lastElement(); sbuf = null; } continue; } if (sbuf != null) sbuf.append( chars[i] ); } } // convert if ( count == 0 ) count = array.size(); index--; 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); } /** * Convert ArrayList to array. */ private Object[] buildArray (ArrayList _input, int index, int count) throws SQLException { int i = 0; if (count == -1) count = _input.size(); Object[] retVal = null; boolean multi = false; for (int z = 0; z < count; z++) { Object x = _input.get(z); if (x == null) continue; else if (x instanceof ArrayList) multi = true; break; } switch ( getBaseType() ) { case Types.BIT: retVal = multi ? new Object[count] : new Boolean[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : AbstractJdbc2ResultSet.toBoolean((String) v); } break; case Types.SMALLINT: case Types.INTEGER: retVal = multi ? new Object[count] : new Integer[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : AbstractJdbc2ResultSet.toInt((String) v); } break; case Types.BIGINT: retVal = multi ? new Object[count] : new Long[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : AbstractJdbc2ResultSet.toLong((String) v); } break; case Types.NUMERIC: retVal = multi ? new Object[count] : new BigDecimal[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : AbstractJdbc2ResultSet.toBigDecimal((String) v, -1); } break; case Types.REAL: retVal = multi ? new Object[count] : new Float[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : AbstractJdbc2ResultSet.toFloat((String) v); } break; case Types.DOUBLE: retVal = multi ? new Object[count] : new Double[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : AbstractJdbc2ResultSet.toDouble((String) v); } break; case Types.CHAR: case Types.VARCHAR: retVal = multi ? new Object[count] : new String[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : v; } break; case Types.DATE: retVal = multi ? new Object[count] : new java.sql.Date[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : conn.getTimestampUtils().toDate(null, (String) v); } break; case Types.TIME: retVal = multi ? new Object[count] : new java.sql.Time[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : conn.getTimestampUtils().toTime(null, (String) v); } break; case Types.TIMESTAMP: retVal = multi ? new Object[count] : new java.sql.Timestamp[count]; for ( ; count > 0; count-- ) { Object v = _input.get(index++); retVal[i++] = multi && v != null ? buildArray((ArrayList) v, 0, -1) : conn.getTimestampUtils().toTimestamp(null, (String) v); } break; // Other datatypes not currently supported. If you are really using other types ask // yourself if an array of non-trivial data types is really good database design. default: if (conn.getLogger().logDebug()) conn.getLogger().debug("getArrayImpl(long,int,Map) with "+getBaseTypeName()); throw org.postgresql.Driver.notImplemented(this.getClass(), "getArrayImpl(long,int,Map)"); } return retVal; } public int getBaseType() throws SQLException { return conn.getSQLType(getBaseTypeName()); } public String getBaseTypeName() throws SQLException { String fType = conn.getPGType(field.getOID()); if ( fType.charAt(0) == '_' ) fType = fType.substring(1); return fType; } 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 ); } private void fillIntegerResultSet(long index, int[] intArray, Vector rows) throws SQLException { for ( int i = 0; i < intArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( Integer.toString(intArray[i]) ); // Value rows.addElement(tuple); } } private void fillStringResultSet(long index, String[] strArray, Vector rows) throws SQLException { for ( int i = 0; i < strArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( strArray[i] ); // Value rows.addElement(tuple); } } public java.sql.ResultSet getResultSetImpl(long index, int count, java.util.Map map) throws SQLException { Object array = getArrayImpl( index, count, map ); Vector rows = new Vector(); Field[] fields = new Field[2]; fields[0] = new Field("INDEX", Oid.INT2); switch ( getBaseType() ) { case Types.BIT: boolean[] booleanArray = (boolean[]) array; fields[1] = new Field("VALUE", Oid.BOOL); for ( int i = 0; i < booleanArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( (booleanArray[i] ? "YES" : "NO") ); // Value rows.addElement(tuple); } break; case Types.SMALLINT: fields[1] = new Field("VALUE", Oid.INT2); fillIntegerResultSet(index, (int[])array, rows); break; case Types.INTEGER: fields[1] = new Field("VALUE", Oid.INT4); fillIntegerResultSet(index, (int[])array, rows); break; case Types.BIGINT: long[] longArray = (long[]) array; fields[1] = new Field("VALUE", Oid.INT8); for ( int i = 0; i < longArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( Long.toString(longArray[i]) ); // Value rows.addElement(tuple); } break; case Types.NUMERIC: BigDecimal[] bdArray = (BigDecimal[]) array; fields[1] = new Field("VALUE", Oid.NUMERIC); for ( int i = 0; i < bdArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( bdArray[i].toString() ); // Value rows.addElement(tuple); } break; case Types.REAL: float[] floatArray = (float[]) array; fields[1] = new Field("VALUE", Oid.FLOAT4); for ( int i = 0; i < floatArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( Float.toString(floatArray[i]) ); // Value rows.addElement(tuple); } break; case Types.DOUBLE: double[] doubleArray = (double[]) array; fields[1] = new Field("VALUE", Oid.FLOAT8); for ( int i = 0; i < doubleArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( Double.toString(doubleArray[i]) ); // Value rows.addElement(tuple); } break; case Types.CHAR: fields[1] = new Field("VALUE", Oid.BPCHAR); fillStringResultSet(index, (String[])array, rows); break; case Types.VARCHAR: fields[1] = new Field("VALUE", Oid.VARCHAR); fillStringResultSet(index, (String[])array, rows); break; case Types.DATE: java.sql.Date[] dateArray = (java.sql.Date[]) array; fields[1] = new Field("VALUE", Oid.DATE); for ( int i = 0; i < dateArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( conn.getTimestampUtils().toString(null, dateArray[i]) ); // Value rows.addElement(tuple); } break; case Types.TIME: java.sql.Time[] timeArray = (java.sql.Time[]) array; fields[1] = new Field("VALUE", Oid.TIME); for ( int i = 0; i < timeArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( conn.getTimestampUtils().toString(null, timeArray[i]) ); // Value rows.addElement(tuple); } break; case Types.TIMESTAMP: java.sql.Timestamp[] timestampArray = (java.sql.Timestamp[]) array; fields[1] = new Field("VALUE", Oid.TIMESTAMPTZ); for ( int i = 0; i < timestampArray.length; i++ ) { byte[][] tuple = new byte[2][0]; tuple[0] = conn.encodeString( Integer.toString((int)index + i) ); // Index tuple[1] = conn.encodeString( conn.getTimestampUtils().toString(null, timestampArray[i]) ); // Value rows.addElement(tuple); } break; // Other datatypes not currently supported. If you are really using other types ask // yourself if an array of non-trivial data types is really good database design. default: if (conn.getLogger().logDebug()) conn.getLogger().debug("getResultSetImpl(long,int,Map) with "+getBaseTypeName()); throw org.postgresql.Driver.notImplemented(this.getClass(), "getResultSetImpl(long,int,Map)"); } BaseStatement stat = (BaseStatement) conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); return (ResultSet) stat.createDriverResultSet(fields, rows); } public String toString() { return rawString; } }