Re: JDBC Statement.executeBatch patch - Mailing list pgsql-patches

From Barry Lind
Subject Re: JDBC Statement.executeBatch patch
Date
Msg-id 3B8BC6B4.6070303@xythos.com
Whole thread Raw
In response to JDBC Statement.executeBatch patch  (Rene Pijlman <rpijlman@wanadoo.nl>)
List pgsql-patches
Rene,

The patch looks good.  Thanks for your time and effort.

thanks,
--Barry

Rene Pijlman wrote:
> Attached is a patch for current CVS, consisting of a cvs diff -c
> for the changed files and a few new files:
> - test/jdbc2/BatchExecuteTest.java
> - util/MessageTranslator.java
> - jdbc2/PBatchUpdateException.java
>
> As an aside, is this the best way to submit a patch consisting
> of both changed and new files? Or is there a smarter cvs command
> which gets them all in one patch file?
>
> This patch fixes batch processing in the JDBC driver to be
> JDBC-2 compliant. Specifically, the changes introduced by this
> patch are:
>
> 1) Statement.executeBatch() no longer commits or rolls back a
> transaction, as this is not prescribed by the JDBC spec. Its up
> to the application to disable autocommit and to commit or
> rollback the transaction. Where JDBC talks about "executing the
> statements as a unit", it means executing the statements in one
> round trip to the backend for better performance, it does not
> mean executing the statements in a transaction.
>
> 2) Statement.executeBatch() now throws a BatchUpdateException()
> as required by the JDBC spec. The significance of this is that
> the receiver of the exception gets the updateCounts of the
> commands that succeeded before the error occurred. In order for
> the messages to be translatable, java.sql.BatchUpdateException
> is extended by org.postgresql.jdbc2.PBatchUpdateException() and
> the localization code is factored out from
> org.postgresql.util.PSQLException to a separate singleton class
> org.postgresql.util.MessageTranslator.
>
> 3) When there is no batch or there are 0 statements in the batch
> when Statement.executeBatch() is called, do not throw an
> SQLException, but silently do nothing and return an update count
> array of length 0. The JDBC spec says "Throws an SQLException if
> the driver does not support batch statements", which is clearly
> not the case. See testExecuteEmptyBatch() in
> BatchExecuteTest.java for an example. The message
> postgresql.stat.batch.empty is removed from the language
> specific properties files.
>
> 4) When Statement.executeBatch() is performed, reset the
> statement's list of batch commands to empty. The JDBC spec isn't
> 100% clear about this. This behaviour is only documented in the
> Java tutorial
> (http://java.sun.com/docs/books/tutorial/jdbc/jdbc2dot0/batchupdates.html).
> Note that the Oracle JDBC driver also resets the statement's
> list in executeBatch(), and this seems the most reasonable
> interpretation.
>
> 5) A new test case is added to the JDBC test suite which tests
> various aspects of batch processing. See the new file
> BatchExecuteTest.java.
>
> Regards,
> René Pijlman
>
>
> ------------------------------------------------------------------------
>
> 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.6
> diff -c -r1.6 errors.properties
> *** src/interfaces/jdbc/org/postgresql/errors.properties    2001/08/21 00:37:23    1.6
> --- src/interfaces/jdbc/org/postgresql/errors.properties    2001/08/26 18:20:15
> ***************
> *** 61,67 ****
>   postgresql.serial.noclass:No class found for {0}.
>   postgresql.serial.table:The table for {0} is not in the database. Contact the DBA, as the database is in an
inconsistentstate. 
>   postgresql.serial.underscore:Class names may not have _ in them. You supplied {0}.
> - postgresql.stat.batch.empty:The batch is empty. There is nothing to execute.
>   postgresql.stat.batch.error:Batch entry {0} {1} was aborted.
>   postgresql.stat.maxfieldsize:An attempt to setMaxFieldSize() failed - compile time default in force.
>   postgresql.stat.noresult:No results were returned by the query.
> --- 61,66 ----
> Index: src/interfaces/jdbc/org/postgresql/errors_de.properties
> ===================================================================
> RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/errors_de.properties,v
> retrieving revision 1.1
> diff -c -r1.1 errors_de.properties
> *** src/interfaces/jdbc/org/postgresql/errors_de.properties    2001/07/09 20:25:44    1.1
> --- src/interfaces/jdbc/org/postgresql/errors_de.properties    2001/08/26 18:20:15
> ***************
> *** 65,71 ****
>   postgresql.serial.noclass:Keine Klasse für Typ »{0}« gefunden
>   postgresql.serial.table:Keine Tabelle für Typ »{0}« in der Datenbank gefunden. Die Datenbank ist in einem
unbeständigenZustand. 
>   postgresql.serial.underscore:Zu serialisierende Klassennamen dürfen keine Unterstriche (_) enthälten. Der
angegebeneName war »{0}«. 
> - postgresql.stat.batch.empty:Der Anweisungs-Batch ist leer.
>   postgresql.stat.batch.error:Batch-Anweisung Nummer {0} ({1}) wurde abgebrochen.
>   postgresql.stat.maxfieldsize:setMaxFieldSize() is nicht möglich; die Grenze ist fest eingebaut.
>   postgresql.stat.noresult:Die Abfrage ergab kein Ergebnis.
> --- 65,70 ----
> Index: src/interfaces/jdbc/org/postgresql/errors_it.properties
> ===================================================================
> RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/errors_it.properties,v
> retrieving revision 1.1
> diff -c -r1.1 errors_it.properties
> *** src/interfaces/jdbc/org/postgresql/errors_it.properties    2000/10/12 08:55:24    1.1
> --- src/interfaces/jdbc/org/postgresql/errors_it.properties    2001/08/26 18:20:15
> ***************
> *** 60,66 ****
>   postgresql.serial.noclass:Nessuna classe trovata per {0}.
>   postgresql.serial.table:La tabella per {0} non è nel database. Contattare il DBA, visto che il database è in uno
statoincosistente. 
>   postgresql.serial.underscore:Il nome di una classe non può contenere il carattere ``_''. E` stato fornito {0}.
> - postgresql.stat.batch.empty:La sequenza di operazioni è vuota. Non c'è niente da eseguire.
>   postgresql.stat.batch.error:L'operazione {0} {1} della sequenza è stata annullata.
>   postgresql.stat.maxfieldsize:Fallito un tentativo a setMaxFieldSize() - verrà utilizzato il valore predefinito a
tempodi compilazione. 
>   postgresql.stat.noresult:Nessun risultato è stato restituito dalla query.
> --- 60,65 ----
> Index: src/interfaces/jdbc/org/postgresql/errors_nl.properties
> ===================================================================
> RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/errors_nl.properties,v
> retrieving revision 1.1
> diff -c -r1.1 errors_nl.properties
> *** src/interfaces/jdbc/org/postgresql/errors_nl.properties    2000/04/26 05:39:32    1.1
> --- src/interfaces/jdbc/org/postgresql/errors_nl.properties    2001/08/26 18:20:15
> ***************
> *** 54,60 ****
>   postgresql.serial.noclass:Geen class gevonden voor {0}.
>
>   postgresql.serial.table:De tabel voor {0} is niet in de database. Neem contact op met de DBA, omdat de database in
eeninconsistente staat verkeert. 
>
>   postgresql.serial.underscore:Class namen mogen geen _ in zich hebben. Jij voerde {0} in.
>
> - postgresql.stat.batch.empty:De batch is leeg. Er is niets om uit te voeren.
>
>   postgresql.stat.batch.error:Batch invoer {0} {1} werd afgebroken.
>
>   postgresql.stat.maxfieldsize:Een poging om setMaxFieldSize() faalde - compiletime standaardwaarde van kracht.
>
>   postgresql.stat.noresult:Geen resultaten werden teruggegeven door de query.
>
> --- 54,59 ----
> Index: src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java
> ===================================================================
> RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java,v
> retrieving revision 1.31
> diff -c -r1.31 DatabaseMetaData.java
> *** src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java    2001/08/24 16:50:17    1.31
> --- src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java    2001/08/26 18:20:15
> ***************
> *** 2836,2842 ****
>       }
>
>       /**
> !      * New in 7.1 - If this is for PreparedStatement yes, ResultSet no
>        */
>       public boolean supportsBatchUpdates() throws SQLException
>       {
> --- 2836,2842 ----
>       }
>
>       /**
> !      * Indicates whether the driver supports batch updates.
>        */
>       public boolean supportsBatchUpdates() throws SQLException
>       {
> Index: src/interfaces/jdbc/org/postgresql/jdbc2/Statement.java
> ===================================================================
> RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc2/Statement.java,v
> retrieving revision 1.11
> diff -c -r1.11 Statement.java
> *** src/interfaces/jdbc/org/postgresql/jdbc2/Statement.java    2001/08/10 14:42:07    1.11
> --- src/interfaces/jdbc/org/postgresql/jdbc2/Statement.java    2001/08/26 18:20:17
> ***************
> *** 179,198 ****
>
>       public int[] executeBatch() throws SQLException
>       {
> !     if(batch==null || batch.isEmpty())
> !         throw new PSQLException("postgresql.stat.batch.empty");
> !
>       int size=batch.size();
>       int[] result=new int[size];
>       int i=0;
> -     this.execute("begin"); // PTM: check this when autoCommit is false
>       try {
>           for(i=0;i<size;i++)
>           result[i]=this.executeUpdate((String)batch.elementAt(i));
> -         this.execute("commit"); // PTM: check this
>       } catch(SQLException e) {
> !         this.execute("abort"); // PTM: check this
> !         throw new PSQLException("postgresql.stat.batch.error",new Integer(i),batch.elementAt(i));
>       }
>       return result;
>       }
> --- 179,204 ----
>
>       public int[] executeBatch() throws SQLException
>       {
> !     if(batch==null)
> !         batch=new Vector();
>       int size=batch.size();
>       int[] result=new int[size];
>       int i=0;
>       try {
>           for(i=0;i<size;i++)
>           result[i]=this.executeUpdate((String)batch.elementAt(i));
>       } catch(SQLException e) {
> !         int[] resultSucceeded = new int[i];
> !         System.arraycopy(result,0,resultSucceeded,0,i);
> !
> !         PBatchUpdateException updex =
> !             new PBatchUpdateException("postgresql.stat.batch.error",
> !                 new Integer(i), batch.elementAt(i), resultSucceeded);
> !         updex.setNextException(e);
> !
> !         throw updex;
> !     } finally {
> !         batch.removeAllElements();
>       }
>       return result;
>       }
> Index: src/interfaces/jdbc/org/postgresql/test/JDBC2Tests.java
> ===================================================================
> RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/test/JDBC2Tests.java,v
> retrieving revision 1.4
> diff -c -r1.4 JDBC2Tests.java
> *** src/interfaces/jdbc/org/postgresql/test/JDBC2Tests.java    2001/07/21 18:52:11    1.4
> --- src/interfaces/jdbc/org/postgresql/test/JDBC2Tests.java    2001/08/26 18:20:17
> ***************
> *** 205,210 ****
> --- 205,214 ----
>       suite.addTestSuite(TimestampTest.class);
>
>       // PreparedStatement
> +     suite.addTestSuite(BatchExecuteTest.class);
> +
> +     // BatchExecute
> +
>
>       // MetaData
>
> Index: src/interfaces/jdbc/org/postgresql/util/PSQLException.java
> ===================================================================
> RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/util/PSQLException.java,v
> retrieving revision 1.4
> diff -c -r1.4 PSQLException.java
> *** src/interfaces/jdbc/org/postgresql/util/PSQLException.java    2001/01/25 09:16:36    1.4
> --- src/interfaces/jdbc/org/postgresql/util/PSQLException.java    2001/08/26 18:20:17
> ***************
> *** 2,9 ****
>
>   import java.io.*;
>   import java.sql.*;
> - import java.text.*;
> - import java.util.*;
>
>   /**
>    * This class extends SQLException, and provides our internationalisation handling
> --- 2,7 ----
> ***************
> *** 12,20 ****
>   {
>       private String message;
>
> -     // Cache for future errors
> -     static ResourceBundle bundle;
> -
>       /**
>        * This provides the same functionality to SQLException
>        * @param error Error string
> --- 10,15 ----
> ***************
> *** 86,122 ****
>       translate(error,argv);
>       }
>
> !     /**
> !      * This does the actual translation
> !      */
> !     private void translate(String id,Object[] args)
> !     {
> !     if(bundle == null) {
> !         try {
> !         bundle = ResourceBundle.getBundle("org.postgresql.errors");
> !         } catch(MissingResourceException e) {
> !                 // translation files have not been installed.
> !                 message = id;
> !         }
>       }
>
> -         if (bundle != null) {
> -     // Now look up a localized message. If one is not found, then use
> -     // the supplied message instead.
> -         message = null;
> -         try {
> -             message = bundle.getString(id);
> -         } catch(MissingResourceException e) {
> -             message = id;
> -         }
> -         }
> -
> -     // Expand any arguments
> -     if(args!=null && message != null)
> -         message = MessageFormat.format(message,args);
> -
> -     }
> -
>       /**
>        * Overides Throwable
>        */
> --- 81,90 ----
>       translate(error,argv);
>       }
>
> !     private void translate(String error, Object[] args) {
> !         message = MessageTranslator.translate(error,args);
>       }
>
>       /**
>        * Overides Throwable
>        */
> ***************
> *** 140,144 ****
>       {
>       return message;
>       }
> -
>   }
> --- 108,111 ----
>
>
> ------------------------------------------------------------------------
>
> package org.postgresql.test.jdbc2;
>
> import org.postgresql.test.JDBC2Tests;
> import junit.framework.TestCase;
> import java.sql.*;
>
> /**
>  * Test case for Statement.batchExecute()
>  */
> public class BatchExecuteTest extends TestCase {
>
>     private Connection con;
>     private Statement stmt;
>
>     public BatchExecuteTest(String name) {
>         super(name);
>     }
>
>     // Set up the fixture for this testcase: a connection to a database with
>     // a table for this test.
>     protected void setUp() throws Exception {
>         con = JDBC2Tests.openDB();
>         stmt = con.createStatement();
>
>         // Drop the test table if it already exists for some reason. It is
>         // not an error if it doesn't exist.
>         try {
>             stmt.executeUpdate("DROP TABLE testbatch");
>         } catch (SQLException e) {
>              // Intentionally ignore. We cannot distinguish "table does not
>              // exist" from other errors, since PostgreSQL doesn't support
>              // error codes yet.
>         }
>
>         stmt.executeUpdate("CREATE TABLE testbatch(pk INTEGER, col1 INTEGER)");
>         stmt.executeUpdate("INSERT INTO testbatch VALUES(1, 0)");
>
>         // Generally recommended with batch updates. By default we run all
>         // tests in this test case with autoCommit disabled.
>         con.setAutoCommit(false);
>     }
>
>     // Tear down the fixture for this test case.
>     protected void tearDown() throws Exception {
>         con.setAutoCommit(true);
>         if (stmt != null) {
>             stmt.executeUpdate("DROP TABLE testbatch");
>             stmt.close();
>         }
>         if (con != null) {
>               JDBC2Tests.closeDB(con);
>         }
>     }
>
>     public void testSupportsBatchUpdates() throws Exception {
>         DatabaseMetaData dbmd = con.getMetaData();
>         assertTrue(dbmd.supportsBatchUpdates());
>     }
>
>     private void assertCol1HasValue(int expected) throws Exception {
>         Statement getCol1 = con.createStatement();
>
>         ResultSet rs =
>             getCol1.executeQuery("SELECT col1 FROM testbatch WHERE pk = 1");
>         assertTrue(rs.next());
>
>         int actual = rs.getInt("col1");
>
>         assertEquals(expected, actual);
>
>         assertEquals(false, rs.next());
>
>         rs.close();
>         getCol1.close();
>     }
>
>     public void testExecuteEmptyBatch() throws Exception {
>         int[] updateCount = stmt.executeBatch();
>         assertEquals(0,updateCount.length);
>
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1");
>         stmt.clearBatch();
>         updateCount = stmt.executeBatch();
>         assertEquals(0,updateCount.length);
>     }
>
>     public void testClearBatch() throws Exception {
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1");
>         assertCol1HasValue(0);
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 2 WHERE pk = 1");
>         assertCol1HasValue(0);
>         stmt.clearBatch();
>         assertCol1HasValue(0);
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 4 WHERE pk = 1");
>         assertCol1HasValue(0);
>         stmt.executeBatch();
>         assertCol1HasValue(4);
>         con.commit();
>         assertCol1HasValue(4);
>     }
>
>     public void testSelectThrowsException() throws Exception {
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1");
>         stmt.addBatch("SELECT col1 FROM testbatch WHERE pk = 1");
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 2 WHERE pk = 1");
>
>         try {
>             stmt.executeBatch();
>             fail("Should raise a BatchUpdateException because of the SELECT");
>         } catch (BatchUpdateException e) {
>             int [] updateCounts = e.getUpdateCounts();
>             assertEquals(1,updateCounts.length);
>             assertEquals(1,updateCounts[0]);
>         } catch (SQLException e) {
>             fail( "Should throw a BatchUpdateException instead of " +
>                   "a generic SQLException: " + e);
>         }
>     }
>
>     public void testPreparedStatement() throws Exception {
>         PreparedStatement pstmt = con.prepareStatement(
>             "UPDATE testbatch SET col1 = col1 + ? WHERE PK = ?" );
>
>         // Note that the first parameter changes for every statement in the
>         // batch, whereas the second parameter remains constant.
>         pstmt.setInt(1,1);
>         pstmt.setInt(2,1);
>         pstmt.addBatch();
>         assertCol1HasValue(0);
>
>         pstmt.setInt(1,2);
>         pstmt.addBatch();
>         assertCol1HasValue(0);
>
>         pstmt.setInt(1,4);
>         pstmt.addBatch();
>         assertCol1HasValue(0);
>
>         pstmt.executeBatch();
>         assertCol1HasValue(7);
>
>         con.commit();
>         assertCol1HasValue(7);
>
>         con.rollback();
>         assertCol1HasValue(7);
>
>         pstmt.close();
>     }
>
>     /**
>     */
>     public void testTransactionalBehaviour() throws Exception {
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1");
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 2 WHERE pk = 1");
>         stmt.executeBatch();
>         con.rollback();
>         assertCol1HasValue(0);
>
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 4 WHERE pk = 1");
>         stmt.addBatch("UPDATE testbatch SET col1 = col1 + 8 WHERE pk = 1");
>
>         // The statement has been added to the batch, but it should not yet
>         // have been executed.
>         assertCol1HasValue(0);
>
>         int[] updateCounts = stmt.executeBatch();
>         assertEquals(2,updateCounts.length);
>         assertEquals(1,updateCounts[0]);
>         assertEquals(1,updateCounts[1]);
>
>         assertCol1HasValue(12);
>         con.commit();
>         assertCol1HasValue(12);
>         con.rollback();
>         assertCol1HasValue(12);
>     }
> }
>
> /* TODO tests that can be added to this test case
>    - SQLExceptions chained to a BatchUpdateException
>    - test PreparedStatement as thoroughly as Statement
>  */
>
>
> ------------------------------------------------------------------------
>
> package org.postgresql.util;
>
> import java.util.*;
> import java.text.*;
>
> /**
>  * A singleton class to translate JDBC driver messages in SQLException's.
>  */
> public class MessageTranslator {
>
>     // The singleton instance.
>     private static MessageTranslator instance = null;
>
>     private ResourceBundle bundle;
>
>     private MessageTranslator() {
>         try {
>             bundle = ResourceBundle.getBundle("org.postgresql.errors");
>         } catch(MissingResourceException e) {
>             // translation files have not been installed.
>             bundle = null;
>         }
>     }
>
>     // Synchronized, otherwise multiple threads may perform the test and
>     // assign to the singleton instance simultaneously.
>     private synchronized final static MessageTranslator getInstance() {
>         if (instance == null) {
>             instance = new MessageTranslator();
>         }
>         return instance;
>     }
>
>     public final static String translate(String id, Object[] args) {
>
>         MessageTranslator translator = MessageTranslator.getInstance();
>
>         return translator._translate(id, args);
>     }
>
>     private final String _translate(String id, Object[] args) {
>         String message;
>
>         if (bundle != null && id != null) {
>             // Now look up a localized message. If one is not found, then use
>             // the supplied message instead.
>             try {
>                 message = bundle.getString(id);
>             } catch(MissingResourceException e) {
>                 message = id;
>             }
>         } else {
>             message = id;
>         }
>
>         // Expand any arguments
>         if (args != null && message != null) {
>             message = MessageFormat.format(message,args);
>         }
>
>         return message;
>     }
> }
>
>
> ------------------------------------------------------------------------
>
> package org.postgresql.jdbc2;
>
> import org.postgresql.util.*;
> import java.sql.*;
>
> /**
>  * This class extends java.sql.BatchUpdateException, and provides our
>  * internationalisation handling.
>  */
> class PBatchUpdateException extends java.sql.BatchUpdateException {
>
>     private String message;
>
>     public PBatchUpdateException(
>         String error, Object arg1, Object arg2, int[] updateCounts ) {
>
>         super(updateCounts);
>
>         Object[] argv = new Object[2];
>         argv[0] = arg1;
>         argv[1] = arg2;
>         translate(error,argv);
>     }
>
>     private void translate(String error, Object[] args) {
>         message = MessageTranslator.translate(error,args);
>     }
>
>     // Overides Throwable
>     public String getLocalizedMessage()
>     {
>         return message;
>     }
>
>     // Overides Throwable
>     public String getMessage()
>     {
>         return message;
>     }
>
>     // Overides Object
>     public String toString()
>     {
>         return message;
>     }
> }
>
>
> ------------------------------------------------------------------------
>
>
> ---------------------------(end of broadcast)---------------------------
> TIP 3: if posting/reading through Usenet, please send an appropriate
> subscribe-nomail command to majordomo@postgresql.org so that your
> message can get through to the mailing list cleanly
>
> patch.diff
>
> Content-Type:
>
> text/plain
> Content-Encoding:
>
> quoted-printable
>
>
> ------------------------------------------------------------------------
> BatchExecuteTest.java
>
> Content-Type:
>
> text/plain
> Content-Encoding:
>
> quoted-printable
>
>
> ------------------------------------------------------------------------
> MessageTranslator.java
>
> Content-Type:
>
> text/plain
> Content-Encoding:
>
> quoted-printable
>
>
> ------------------------------------------------------------------------
> PBatchUpdateException.java
>
> Content-Type:
>
> text/plain
> Content-Encoding:
>
> quoted-printable
>
>
> ------------------------------------------------------------------------
> Part 1.6
>
> Content-Type:
>
> text/plain
> Content-Encoding:
>
> binary
>
>



pgsql-patches by date:

Previous
From: Bruce Momjian
Date:
Subject: Re: [JDBC] JDBC Statement.executeBatch patch
Next
From: Bruce Momjian
Date:
Subject: Re: [JDBC] Attempt to clean up ExecSql() in JDBC