Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit d99794e

Browse files
committed
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
1 parent e5d3df2 commit d99794e

File tree

6 files changed

+266
-43
lines changed

6 files changed

+266
-43
lines changed

src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2836,7 +2836,7 @@ public boolean insertsAreDetected(int type) throws SQLException
28362836
}
28372837

28382838
/**
2839-
* New in 7.1 - If this is for PreparedStatement yes, ResultSet no
2839+
* Indicates whether the driver supports batch updates.
28402840
*/
28412841
public boolean supportsBatchUpdates() throws SQLException
28422842
{

src/interfaces/jdbc/org/postgresql/jdbc2/Statement.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,20 +179,26 @@ public void clearBatch() throws SQLException
179179

180180
public int[] executeBatch() throws SQLException
181181
{
182-
if(batch==null || batch.isEmpty())
183-
throw new PSQLException("postgresql.stat.batch.empty");
184-
182+
if(batch==null)
183+
batch=new Vector();
185184
int size=batch.size();
186185
int[] result=new int[size];
187186
int i=0;
188-
this.execute("begin"); // PTM: check this when autoCommit is false
189187
try {
190188
for(i=0;i<size;i++)
191189
result[i]=this.executeUpdate((String)batch.elementAt(i));
192-
this.execute("commit"); // PTM: check this
193190
} catch(SQLException e) {
194-
this.execute("abort"); // PTM: check this
195-
throw new PSQLException("postgresql.stat.batch.error",new Integer(i),batch.elementAt(i));
191+
int[] resultSucceeded = new int[i];
192+
System.arraycopy(result,0,resultSucceeded,0,i);
193+
194+
PBatchUpdateException updex =
195+
new PBatchUpdateException("postgresql.stat.batch.error",
196+
new Integer(i), batch.elementAt(i), resultSucceeded);
197+
updex.setNextException(e);
198+
199+
throw updex;
200+
} finally {
201+
batch.removeAllElements();
196202
}
197203
return result;
198204
}

src/interfaces/jdbc/org/postgresql/test/JDBC2Tests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ public static TestSuite suite() {
205205
suite.addTestSuite(TimestampTest.class);
206206

207207
// PreparedStatement
208+
suite.addTestSuite(BatchExecuteTest.class);
209+
210+
// BatchExecute
211+
208212

209213
// MetaData
210214

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package org.postgresql.test.jdbc2;
2+
3+
import org.postgresql.test.JDBC2Tests;
4+
import junit.framework.TestCase;
5+
import java.sql.*;
6+
7+
/**
8+
* Test case for Statement.batchExecute()
9+
*/
10+
public class BatchExecuteTest extends TestCase {
11+
12+
private Connection con;
13+
private Statement stmt;
14+
15+
public BatchExecuteTest(String name) {
16+
super(name);
17+
}
18+
19+
// Set up the fixture for this testcase: a connection to a database with
20+
// a table for this test.
21+
protected void setUp() throws Exception {
22+
con = JDBC2Tests.openDB();
23+
stmt = con.createStatement();
24+
25+
// Drop the test table if it already exists for some reason. It is
26+
// not an error if it doesn't exist.
27+
try {
28+
stmt.executeUpdate("DROP TABLE testbatch");
29+
} catch (SQLException e) {
30+
// Intentionally ignore. We cannot distinguish "table does not
31+
// exist" from other errors, since PostgreSQL doesn't support
32+
// error codes yet.
33+
}
34+
35+
stmt.executeUpdate("CREATE TABLE testbatch(pk INTEGER, col1 INTEGER)");
36+
stmt.executeUpdate("INSERT INTO testbatch VALUES(1, 0)");
37+
38+
// Generally recommended with batch updates. By default we run all
39+
// tests in this test case with autoCommit disabled.
40+
con.setAutoCommit(false);
41+
}
42+
43+
// Tear down the fixture for this test case.
44+
protected void tearDown() throws Exception {
45+
con.setAutoCommit(true);
46+
if (stmt != null) {
47+
stmt.executeUpdate("DROP TABLE testbatch");
48+
stmt.close();
49+
}
50+
if (con != null) {
51+
JDBC2Tests.closeDB(con);
52+
}
53+
}
54+
55+
public void testSupportsBatchUpdates() throws Exception {
56+
DatabaseMetaData dbmd = con.getMetaData();
57+
assertTrue(dbmd.supportsBatchUpdates());
58+
}
59+
60+
private void assertCol1HasValue(int expected) throws Exception {
61+
Statement getCol1 = con.createStatement();
62+
63+
ResultSet rs =
64+
getCol1.executeQuery("SELECT col1 FROM testbatch WHERE pk = 1");
65+
assertTrue(rs.next());
66+
67+
int actual = rs.getInt("col1");
68+
69+
assertEquals(expected, actual);
70+
71+
assertEquals(false, rs.next());
72+
73+
rs.close();
74+
getCol1.close();
75+
}
76+
77+
public void testExecuteEmptyBatch() throws Exception {
78+
int[] updateCount = stmt.executeBatch();
79+
assertEquals(0,updateCount.length);
80+
81+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1");
82+
stmt.clearBatch();
83+
updateCount = stmt.executeBatch();
84+
assertEquals(0,updateCount.length);
85+
}
86+
87+
public void testClearBatch() throws Exception {
88+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1");
89+
assertCol1HasValue(0);
90+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 2 WHERE pk = 1");
91+
assertCol1HasValue(0);
92+
stmt.clearBatch();
93+
assertCol1HasValue(0);
94+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 4 WHERE pk = 1");
95+
assertCol1HasValue(0);
96+
stmt.executeBatch();
97+
assertCol1HasValue(4);
98+
con.commit();
99+
assertCol1HasValue(4);
100+
}
101+
102+
public void testSelectThrowsException() throws Exception {
103+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1");
104+
stmt.addBatch("SELECT col1 FROM testbatch WHERE pk = 1");
105+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 2 WHERE pk = 1");
106+
107+
try {
108+
stmt.executeBatch();
109+
fail("Should raise a BatchUpdateException because of the SELECT");
110+
} catch (BatchUpdateException e) {
111+
int [] updateCounts = e.getUpdateCounts();
112+
assertEquals(1,updateCounts.length);
113+
assertEquals(1,updateCounts[0]);
114+
} catch (SQLException e) {
115+
fail( "Should throw a BatchUpdateException instead of " +
116+
"a generic SQLException: " + e);
117+
}
118+
}
119+
120+
public void testPreparedStatement() throws Exception {
121+
PreparedStatement pstmt = con.prepareStatement(
122+
"UPDATE testbatch SET col1 = col1 + ? WHERE PK = ?" );
123+
124+
// Note that the first parameter changes for every statement in the
125+
// batch, whereas the second parameter remains constant.
126+
pstmt.setInt(1,1);
127+
pstmt.setInt(2,1);
128+
pstmt.addBatch();
129+
assertCol1HasValue(0);
130+
131+
pstmt.setInt(1,2);
132+
pstmt.addBatch();
133+
assertCol1HasValue(0);
134+
135+
pstmt.setInt(1,4);
136+
pstmt.addBatch();
137+
assertCol1HasValue(0);
138+
139+
pstmt.executeBatch();
140+
assertCol1HasValue(7);
141+
142+
con.commit();
143+
assertCol1HasValue(7);
144+
145+
con.rollback();
146+
assertCol1HasValue(7);
147+
148+
pstmt.close();
149+
}
150+
151+
/**
152+
*/
153+
public void testTransactionalBehaviour() throws Exception {
154+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1");
155+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 2 WHERE pk = 1");
156+
stmt.executeBatch();
157+
con.rollback();
158+
assertCol1HasValue(0);
159+
160+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 4 WHERE pk = 1");
161+
stmt.addBatch("UPDATE testbatch SET col1 = col1 + 8 WHERE pk = 1");
162+
163+
// The statement has been added to the batch, but it should not yet
164+
// have been executed.
165+
assertCol1HasValue(0);
166+
167+
int[] updateCounts = stmt.executeBatch();
168+
assertEquals(2,updateCounts.length);
169+
assertEquals(1,updateCounts[0]);
170+
assertEquals(1,updateCounts[1]);
171+
172+
assertCol1HasValue(12);
173+
con.commit();
174+
assertCol1HasValue(12);
175+
con.rollback();
176+
assertCol1HasValue(12);
177+
}
178+
}
179+
180+
/* TODO tests that can be added to this test case
181+
- SQLExceptions chained to a BatchUpdateException
182+
- test PreparedStatement as thoroughly as Statement
183+
*/
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.postgresql.util;
2+
3+
import java.util.*;
4+
import java.text.*;
5+
6+
/**
7+
* A singleton class to translate JDBC driver messages in SQLException's.
8+
*/
9+
public class MessageTranslator {
10+
11+
// The singleton instance.
12+
private static MessageTranslator instance = null;
13+
14+
private ResourceBundle bundle;
15+
16+
private MessageTranslator() {
17+
try {
18+
bundle = ResourceBundle.getBundle("org.postgresql.errors");
19+
} catch(MissingResourceException e) {
20+
// translation files have not been installed.
21+
bundle = null;
22+
}
23+
}
24+
25+
// Synchronized, otherwise multiple threads may perform the test and
26+
// assign to the singleton instance simultaneously.
27+
private synchronized final static MessageTranslator getInstance() {
28+
if (instance == null) {
29+
instance = new MessageTranslator();
30+
}
31+
return instance;
32+
}
33+
34+
public final static String translate(String id, Object[] args) {
35+
36+
MessageTranslator translator = MessageTranslator.getInstance();
37+
38+
return translator._translate(id, args);
39+
}
40+
41+
private final String _translate(String id, Object[] args) {
42+
String message;
43+
44+
if (bundle != null && id != null) {
45+
// Now look up a localized message. If one is not found, then use
46+
// the supplied message instead.
47+
try {
48+
message = bundle.getString(id);
49+
} catch(MissingResourceException e) {
50+
message = id;
51+
}
52+
} else {
53+
message = id;
54+
}
55+
56+
// Expand any arguments
57+
if (args != null && message != null) {
58+
message = MessageFormat.format(message,args);
59+
}
60+
61+
return message;
62+
}
63+
}

src/interfaces/jdbc/org/postgresql/util/PSQLException.java

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import java.io.*;
44
import java.sql.*;
5-
import java.text.*;
6-
import java.util.*;
75

86
/**
97
* This class extends SQLException, and provides our internationalisation handling
@@ -12,9 +10,6 @@ public class PSQLException extends SQLException
1210
{
1311
private String message;
1412

15-
// Cache for future errors
16-
static ResourceBundle bundle;
17-
1813
/**
1914
* This provides the same functionality to SQLException
2015
* @param error Error string
@@ -86,37 +81,10 @@ public PSQLException(String error,Object arg1,Object arg2)
8681
translate(error,argv);
8782
}
8883

89-
/**
90-
* This does the actual translation
91-
*/
92-
private void translate(String id,Object[] args)
93-
{
94-
if(bundle == null) {
95-
try {
96-
bundle = ResourceBundle.getBundle("org.postgresql.errors");
97-
} catch(MissingResourceException e) {
98-
// translation files have not been installed.
99-
message = id;
100-
}
84+
private void translate(String error, Object[] args) {
85+
message = MessageTranslator.translate(error,args);
10186
}
10287

103-
if (bundle != null) {
104-
// Now look up a localized message. If one is not found, then use
105-
// the supplied message instead.
106-
message = null;
107-
try {
108-
message = bundle.getString(id);
109-
} catch(MissingResourceException e) {
110-
message = id;
111-
}
112-
}
113-
114-
// Expand any arguments
115-
if(args!=null && message != null)
116-
message = MessageFormat.format(message,args);
117-
118-
}
119-
12088
/**
12189
* Overides Throwable
12290
*/
@@ -140,5 +108,4 @@ public String toString()
140108
{
141109
return message;
142110
}
143-
144111
}

0 commit comments

Comments
 (0)