Re: JDBC Array Support, Take III - Mailing list pgsql-patches

From Bruce Momjian
Subject Re: JDBC Array Support, Take III
Date
Msg-id 200108171445.f7HEj3d21705@candle.pha.pa.us
Whole thread Raw
In response to JDBC Array Support, Take III  ("Greg Zoller" <gzoller@hotmail.com>)
List pgsql-patches
Patch applied.  I have attached a non-gzip version for review.

> Greetings again.
>
> Thanks for your feedback (and patience).  Enclosed is my third
> attempt at a patch to 7.1.2 to support Array.
>
> [I think I've solved the mangled patch problem.  Hotmail seems to
> try to format the text file, so gzipping it should solve this
> problem.]
>
> In this patch I've incorporated Barry's feedback.  Specifically:
>
> 1)  OIDs are no longer hard-coded into Array.java.  In order to
>     support this change I added a getOID(String) method to Field.java
>     which receives a PostgreSQL field type and returns a value from
>     java.sql.Types.  I couldn't get away from using OIDs altogether
>     because the JDBC spec for Array specifies that some methods return
>     a ResultSet.  This requires I construct Field objects,
>     which means I need OIDs.  At least this approach doesn't hard
>     code these values.  A Hashtable cache has been added to Field
>     so that an SQL lookup isn't necessary (following the model already
>     in Field.java).
>
> 2)  Rewired the base formatting code in ResultSet.java to use 'to'
>     methods, which are then exposed as static methods in ResultSet.
>     These methods are used in Array to format the data without
>     duplications in the code.
>
> 3)  Artifact call to first() in ResultSet.getArray() removed.
>
> Patching as before: (gunzip first this time)
>
> >cd <PGSQL_SRC_PATH>/src/interfaces/jdbc/org/postgresql
> >patch -p1 < $PATH_TO_PATCH/patch
> >cd <PGSQL_SRC_PATH>
> >make
>
> Let me know if I'm getting closer to something useful.
>
> Take care.
> Greg
>
> _________________________________________________________________
> Get your FREE download of MSN Explorer at http://explorer.msn.com/intl.asp

[ Attachment, skipping... ]

>
> ---------------------------(end of broadcast)---------------------------
> TIP 2: you can get off all lists at once with the unregister command
>     (send "unregister YourEmailAddressHere" to majordomo@postgresql.org)

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
Index: src/interfaces/jdbc/org/postgresql/Field.java
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/Field.java,v
retrieving revision 1.4
diff -c -r1.4 Field.java
*** src/interfaces/jdbc/org/postgresql/Field.java    2001/02/13 16:39:00    1.4
--- src/interfaces/jdbc/org/postgresql/Field.java    2001/08/17 14:43:48
***************
*** 22,27 ****
--- 22,29 ----
    public int sql_type = -1;    // The entry in java.sql.Types for this field
    public String type_name = null;// The sql type name

+   private static Hashtable oidCache = new Hashtable();
+
    /**
     * Construct a field based on the information fed to it.
     *
***************
*** 105,110 ****
--- 107,139 ----
    }

    /**
+    * This returns the oid for a field of a given data type
+    * @param type_name PostgreSQL type name
+    * @return PostgreSQL oid value for a field of this type
+    */
+   public int getOID( String type_name ) throws SQLException
+   {
+     int oid = -1;
+     if(type_name != null) {
+         Integer oidValue = (Integer) oidCache.get( type_name );
+         if( oidValue != null )
+             oid = oidValue.intValue();
+         else {
+             // it's not in the cache, so perform a query, and add the result to the cache
+             ResultSet result = (org.postgresql.ResultSet)conn.ExecSQL("select oid from pg_type where typname='"
+                 + type_name + "'");
+             if (result.getColumnCount() != 1 || result.getTupleCount() != 1)
+                 throw new PSQLException("postgresql.unexpected");
+             result.next();
+             oid = Integer.parseInt(result.getString(1));
+             oidCache.put( type_name, new Integer(oid) );
+             result.close();
+         }
+     }
+     return oid;
+   }
+
+   /**
     * This table holds the org.postgresql names for the types supported.
     * Any types that map to Types.OTHER (eg POINT) don't go into this table.
     * They default automatically to Types.OTHER
***************
*** 126,132 ****
      "bool",
      "date",
      "time",
!     "abstime","timestamp"
    };

    /**
--- 155,163 ----
      "bool",
      "date",
      "time",
!     "abstime","timestamp",
!     "_bool", "_char", "_int2", "_int4", "_text", "_oid", "_varchar", "_int8",
!     "_float4", "_float8", "_abstime", "_date", "_time", "_timestamp", "_numeric"
    };

    /**
***************
*** 149,155 ****
      Types.BIT,
      Types.DATE,
      Types.TIME,
!     Types.TIMESTAMP,Types.TIMESTAMP
    };

    /**
--- 180,188 ----
      Types.BIT,
      Types.DATE,
      Types.TIME,
!     Types.TIMESTAMP,Types.TIMESTAMP,
!     Types.ARRAY, Types.ARRAY, Types.ARRAY, Types.ARRAY, Types.ARRAY, Types.ARRAY, Types.ARRAY, Types.ARRAY,
!     Types.ARRAY, Types.ARRAY, Types.ARRAY, Types.ARRAY, Types.ARRAY, Types.ARRAY, Types.ARRAY
    };

    /**
Index: src/interfaces/jdbc/org/postgresql/errors.properties
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/errors.properties,v
retrieving revision 1.4
diff -c -r1.4 errors.properties
*** src/interfaces/jdbc/org/postgresql/errors.properties    2001/01/18 17:37:13    1.4
--- src/interfaces/jdbc/org/postgresql/errors.properties    2001/08/17 14:43:48
***************
*** 1,4 ****
--- 1,5 ----
  # This is the default errors
+ postgresql.arr.range:The array index is out of range.
  postgresql.drv.version:An internal error has occured. Please recompile the driver.
  postgresql.con.auth:The authentication type {0} is not supported. Check that you have configured the pg_hba.conf file
toinclude the client's IP address or Subnet, and that it is using an authentication scheme supported by the driver. 
  postgresql.con.authfail:An error occured while getting the authentication request.
Index: src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java,v
retrieving revision 1.28
diff -c -r1.28 ResultSet.java
*** src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java    2001/07/21 18:52:11    1.28
--- src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java    2001/08/17 14:43:49
***************
*** 61,70 ****
  {
    protected org.postgresql.jdbc2.Statement statement;

!   /**
!    * StringBuffer used by getTimestamp
!    */
!   private StringBuffer sbuf;

    /**
     * Create a new ResultSet - Note that we create ResultSets to
--- 61,67 ----
  {
    protected org.postgresql.jdbc2.Statement statement;

!   private StringBuffer sbuf = null;

    /**
     * Create a new ResultSet - Note that we create ResultSets to
***************
*** 185,198 ****
     */
    public boolean getBoolean(int columnIndex) throws SQLException
    {
!     String s = getString(columnIndex);
!
!     if (s != null)
!       {
!     int c = s.charAt(0);
!     return ((c == 't') || (c == 'T') || (c == '1'));
!       }
!     return false;        // SQL NULL
    }

    /**
--- 182,188 ----
     */
    public boolean getBoolean(int columnIndex) throws SQLException
    {
!     return toBoolean( getString(columnIndex) );
    }

    /**
***************
*** 250,267 ****
     */
    public int getInt(int columnIndex) throws SQLException
    {
!     String s = getFixedString(columnIndex);
!
!     if (s != null)
!       {
!     try
!       {
!         return Integer.parseInt(s);
!       } catch (NumberFormatException e) {
!         throw new PSQLException ("postgresql.res.badint",s);
!       }
!       }
!     return 0;        // SQL NULL
    }

    /**
--- 240,246 ----
     */
    public int getInt(int columnIndex) throws SQLException
    {
!     return toInt( getFixedString(columnIndex) );
    }

    /**
***************
*** 273,290 ****
     */
    public long getLong(int columnIndex) throws SQLException
    {
!     String s = getFixedString(columnIndex);
!
!     if (s != null)
!       {
!     try
!       {
!         return Long.parseLong(s);
!       } catch (NumberFormatException e) {
!         throw new PSQLException ("postgresql.res.badlong",s);
!       }
!       }
!     return 0;        // SQL NULL
    }

    /**
--- 252,258 ----
     */
    public long getLong(int columnIndex) throws SQLException
    {
!     return toLong( getFixedString(columnIndex) );
    }

    /**
***************
*** 296,313 ****
     */
    public float getFloat(int columnIndex) throws SQLException
    {
!     String s = getFixedString(columnIndex);
!
!     if (s != null)
!       {
!     try
!       {
!         return Float.valueOf(s).floatValue();
!       } catch (NumberFormatException e) {
!         throw new PSQLException ("postgresql.res.badfloat",s);
!       }
!       }
!     return 0;        // SQL NULL
    }

    /**
--- 264,270 ----
     */
    public float getFloat(int columnIndex) throws SQLException
    {
!     return toFloat( getFixedString(columnIndex) );
    }

    /**
***************
*** 319,336 ****
     */
    public double getDouble(int columnIndex) throws SQLException
    {
!     String s = getFixedString(columnIndex);
!
!     if (s != null)
!       {
!     try
!       {
!         return Double.valueOf(s).doubleValue();
!       } catch (NumberFormatException e) {
!         throw new PSQLException ("postgresql.res.baddouble",s);
!       }
!       }
!     return 0;        // SQL NULL
    }

    /**
--- 276,282 ----
     */
    public double getDouble(int columnIndex) throws SQLException
    {
!     return toDouble( getFixedString(columnIndex) );
    }

    /**
***************
*** 345,371 ****
     */
    public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException
    {
!     String s = getFixedString(columnIndex);
!     BigDecimal val;
!
!     if (s != null)
!       {
!
!         try
!           {
!         val = new BigDecimal(s);
!       } catch (NumberFormatException e) {
!         throw new PSQLException ("postgresql.res.badbigdec",s);
!       }
!     if (scale==-1) return val;
!       try
!         {
!           return val.setScale(scale);
!         } catch (ArithmeticException e) {
!           throw new PSQLException ("postgresql.res.badbigdec",s);
!         }
!       }
!     return null;        // SQL NULL
    }

    /**
--- 291,297 ----
     */
    public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException
    {
!     return toBigDecimal( getFixedString(columnIndex), scale );
    }

    /**
***************
*** 412,427 ****
     */
    public java.sql.Date getDate(int columnIndex) throws SQLException
    {
!     String s = getString(columnIndex);
!     if(s==null)
!       return null;
!     // length == 10: SQL Date
!     // length >  10: SQL Timestamp, assumes PGDATESTYLE=ISO
!     try {
!       return java.sql.Date.valueOf((s.length() == 10) ? s : s.substring(0,10));
!     } catch (NumberFormatException e) {
!       throw new PSQLException("postgresql.res.baddate", s);
!     }
    }

    /**
--- 338,344 ----
     */
    public java.sql.Date getDate(int columnIndex) throws SQLException
    {
!     return toDate( getString(columnIndex) );
    }

    /**
***************
*** 434,450 ****
     */
    public Time getTime(int columnIndex) throws SQLException
    {
!     String s = getString(columnIndex);
!
!     if(s==null)
!       return null; // SQL NULL
!     // length == 8: SQL Time
!     // length >  8: SQL Timestamp
!     try {
!       return java.sql.Time.valueOf((s.length() == 8) ? s : s.substring(11,19));
!     } catch (NumberFormatException e) {
!       throw new PSQLException("postgresql.res.badtime",s);
!     }
    }

    /**
--- 351,357 ----
     */
    public Time getTime(int columnIndex) throws SQLException
    {
!     return toTime( getString(columnIndex) );
    }

    /**
***************
*** 457,546 ****
     */
    public Timestamp getTimestamp(int columnIndex) throws SQLException
    {
!     String s = getString(columnIndex);
!     if(s==null)
!     return null;
!
!     boolean subsecond;
!     //if string contains a '.' we have fractional seconds
!     if (s.indexOf('.') == -1) {
!       subsecond = false;
!     } else {
!       subsecond = true;
!     }
!
!     //here we are modifying the string from ISO format to a format java can understand
!     //java expects timezone info as 'GMT-08:00' instead of '-08' in postgres ISO format
!     //and java expects three digits if fractional seconds are present instead of two for postgres
!     //so this code strips off timezone info and adds on the GMT+/-...
!     //as well as adds a third digit for partial seconds if necessary
!     synchronized(this) {
!       // We must be synchronized here incase more theads access the ResultSet
!       // bad practice but possible. Anyhow this is to protect sbuf and
!       // SimpleDateFormat objects
!
!       // First time?
!       if(sbuf==null)
!         sbuf = new StringBuffer();
!
!       sbuf.setLength(0);
!       sbuf.append(s);
!
!       //we are looking to see if the backend has appended on a timezone.
!       //currently postgresql will return +/-HH:MM or +/-HH for timezone offset
!       //(i.e. -06, or +06:30, note the expectation of the leading zero for the
!       //hours, and the use of the : for delimiter between hours and minutes)
!       //if the backend ISO format changes in the future this code will
!       //need to be changed as well
!       char sub = sbuf.charAt(sbuf.length()-3);
!       if (sub == '+' || sub == '-') {
!         //we have found timezone info of format +/-HH
!         sbuf.setLength(sbuf.length()-3);
!         if (subsecond)  {
!           sbuf.append('0').append("GMT").append(s.substring(s.length()-3)).append(":00");
!         } else {
!           sbuf.append("GMT").append(s.substring(s.length()-3)).append(":00");
!         }
!       } else if (sub == ':') {
!         //we may have found timezone info of format +/-HH:MM, or there is no
!         //timezone info at all and this is the : preceding the seconds
!         char sub2 = sbuf.charAt(sbuf.length()-5);
!         if (sub2 == '+' || sub2 == '-') {
!           //we have found timezone info of format +/-HH:MM
!           sbuf.setLength(sbuf.length()-5);
!           if (subsecond)  {
!             sbuf.append('0').append("GMT").append(s.substring(s.length()-5));
!           } else {
!             sbuf.append("GMT").append(s.substring(s.length()-5));
!           }
!         } else if (subsecond) {
!           sbuf.append('0');
!         }
!       } else if (subsecond) {
!         sbuf.append('0');
!       }
!
!       // could optimize this a tad to remove too many object creations...
!       SimpleDateFormat df = null;
!
!       if (sbuf.length()>23 && subsecond) {
!         df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
!       } else if (sbuf.length()>23 && !subsecond) {
!         df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
!       } else if (sbuf.length()>10 && subsecond) {
!         df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
!       } else if (sbuf.length()>10 && !subsecond) {
!         df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
!       } else {
!         df = new SimpleDateFormat("yyyy-MM-dd");
!       }
!
!       try {
!           return new Timestamp(df.parse(sbuf.toString()).getTime());
!       } catch(ParseException e) {
!           throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
!       }
!     }
    }

    /**
--- 364,370 ----
     */
    public Timestamp getTimestamp(int columnIndex) throws SQLException
    {
!     return toTimestamp( getString(columnIndex), this );
    }

    /**
***************
*** 960,973 ****
      return true;
      }

!     public Array getArray(String colName) throws SQLException
      {
      return getArray(findColumn(colName));
      }

!     public Array getArray(int i) throws SQLException
      {
!     throw org.postgresql.Driver.notImplemented();
      }

      public java.math.BigDecimal getBigDecimal(int columnIndex) throws SQLException
--- 784,799 ----
      return true;
      }

!     public java.sql.Array getArray(String colName) throws SQLException
      {
      return getArray(findColumn(colName));
      }

!     public java.sql.Array getArray(int i) throws SQLException
      {
!         if (i < 1 || i > fields.length)
!               throw new PSQLException("postgresql.res.colrange");
!         return (java.sql.Array) new org.postgresql.jdbc2.Array( connection, i, fields[i-1], this );
      }

      public java.math.BigDecimal getBigDecimal(int columnIndex) throws SQLException
***************
*** 1486,1490 ****
--- 1312,1484 ----
        this.statement=statement;
      }

+     //----------------- Formatting Methods -------------------
+
+     public static boolean toBoolean(String s)
+     {
+         if (s != null)
+         {
+             int c = s.charAt(0);
+             return ((c == 't') || (c == 'T'));
+         }
+         return false;        // SQL NULL
+     }
+
+     public static int toInt(String s) throws SQLException
+     {
+         if (s != null)
+         {
+             try
+             {
+                 return Integer.parseInt(s);
+             } catch (NumberFormatException e) {
+                 throw new PSQLException ("postgresql.res.badint",s);
+             }
+         }
+         return 0;        // SQL NULL
+     }
+
+     public static long toLong(String s) throws SQLException
+     {
+         if (s != null)
+         {
+             try
+             {
+                 return Long.parseLong(s);
+             } catch (NumberFormatException e) {
+                 throw new PSQLException ("postgresql.res.badlong",s);
+             }
+         }
+         return 0;        // SQL NULL
+     }
+
+     public static BigDecimal toBigDecimal(String s, int scale) throws SQLException
+     {
+         BigDecimal val;
+         if (s != null)
+         {
+             try
+             {
+                 val = new BigDecimal(s);
+             } catch (NumberFormatException e) {
+                 throw new PSQLException ("postgresql.res.badbigdec",s);
+             }
+             if (scale==-1) return val;
+             try
+             {
+                 return val.setScale(scale);
+             } catch (ArithmeticException e) {
+                 throw new PSQLException ("postgresql.res.badbigdec",s);
+             }
+         }
+         return null;        // SQL NULL
+     }
+
+     public static float toFloat(String s) throws SQLException
+     {
+         if (s != null)
+         {
+             try
+             {
+                 return Float.valueOf(s).floatValue();
+             } catch (NumberFormatException e) {
+                 throw new PSQLException ("postgresql.res.badfloat",s);
+             }
+         }
+         return 0;        // SQL NULL
+     }
+
+     public static double toDouble(String s) throws SQLException
+     {
+         if (s != null)
+         {
+             try
+             {
+                 return Double.valueOf(s).doubleValue();
+             } catch (NumberFormatException e) {
+                 throw new PSQLException ("postgresql.res.baddouble",s);
+             }
+         }
+         return 0;        // SQL NULL
+     }
+
+     public static java.sql.Date toDate(String s) throws SQLException
+     {
+         if(s==null)
+             return null;
+         return java.sql.Date.valueOf(s);
+     }
+
+     public static Time toTime(String s) throws SQLException
+     {
+         if(s==null)
+             return null; // SQL NULL
+         return java.sql.Time.valueOf(s);
+     }
+
+     public static Timestamp toTimestamp(String s, ResultSet resultSet) throws SQLException
+     {
+         if(s==null)
+             return null;
+
+         boolean subsecond;
+         //if string contains a '.' we have fractional seconds
+         if (s.indexOf('.') == -1) {
+             subsecond = false;
+         } else {
+             subsecond = true;
+         }
+
+         //here we are modifying the string from ISO format to a format java can understand
+         //java expects timezone info as 'GMT-08:00' instead of '-08' in postgres ISO format
+         //and java expects three digits if fractional seconds are present instead of two for postgres
+         //so this code strips off timezone info and adds on the GMT+/-...
+         //as well as adds a third digit for partial seconds if necessary
+         synchronized(resultSet) {
+             // We must be synchronized here incase more theads access the ResultSet
+             // bad practice but possible. Anyhow this is to protect sbuf and
+             // SimpleDateFormat objects
+
+             // First time?
+             if(resultSet.sbuf==null)
+                 resultSet.sbuf = new StringBuffer();
+
+             resultSet.sbuf.setLength(0);
+             resultSet.sbuf.append(s);
+
+             char sub = resultSet.sbuf.charAt(resultSet.sbuf.length()-3);
+             if (sub == '+' || sub == '-') {
+                 resultSet.sbuf.setLength(resultSet.sbuf.length()-3);
+                 if (subsecond)  {
+                     resultSet.sbuf.append('0').append("GMT").append(s.substring(s.length()-3)).append(":00");
+                 } else {
+                     resultSet.sbuf.append("GMT").append(s.substring(s.length()-3)).append(":00");
+                 }
+             } else if (subsecond) {
+                 resultSet.sbuf.append('0');
+             }
+
+             // could optimize this a tad to remove too many object creations...
+             SimpleDateFormat df = null;
+
+             if (resultSet.sbuf.length()>23 && subsecond) {
+                 df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
+             } else if (resultSet.sbuf.length()>23 && !subsecond) {
+                 df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
+             } else if (resultSet.sbuf.length()>10 && subsecond) {
+                 df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+             } else if (resultSet.sbuf.length()>10 && !subsecond) {
+                 df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+             } else {
+                 df = new SimpleDateFormat("yyyy-MM-dd");
+             }
+
+             try {
+                 return new Timestamp(df.parse(resultSet.sbuf.toString()).getTime());
+             } catch(ParseException e) {
+                 throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
+             }
+         }
+     }
  }


pgsql-patches by date:

Previous
From: Karel Zak
Date:
Subject: encoding names
Next
From: Bruce Momjian
Date:
Subject: Re: Patch for JDBC to update some comments