Thread: AbstractJdbc2Array

AbstractJdbc2Array

From
Marek Lewczuk
Date:
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();
    }

}