mirror of https://github.com/postgres/postgres
Just a quick patch. This makes the JDBC driver thread safe, which is an
important step towards making the driver compliant, and means that for some Java applications and servlets, only a single database connection is needed, so in a sence this is a nice little show stopper for 6.4 (and should still be backward compatible to 6.3.2). Peter
This commit is contained in:
parent
9042e9d757
commit
25b5faa7cd
|
@ -4,7 +4,7 @@
|
|||
# Makefile for Java JDBC interface
|
||||
#
|
||||
# IDENTIFICATION
|
||||
# $Header: /cvsroot/pgsql/src/interfaces/jdbc/Attic/Makefile,v 1.9 1998/09/03 02:29:41 momjian Exp $
|
||||
# $Header: /cvsroot/pgsql/src/interfaces/jdbc/Attic/Makefile,v 1.10 1998/10/08 00:38:18 momjian Exp $
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
|
@ -137,7 +137,8 @@ EX= example/basic.class \
|
|||
example/datestyle.class \
|
||||
example/psql.class \
|
||||
example/ImageViewer.class \
|
||||
example/metadata.class
|
||||
example/metadata.class \
|
||||
example/threadsafe.class
|
||||
# example/Objects.class
|
||||
|
||||
# This rule builds the examples
|
||||
|
@ -160,7 +161,9 @@ examples: postgresql.jar $(EX)
|
|||
@echo " example.psql Simple java implementation of psql"
|
||||
@echo " example.Objects Demonstrates Object Serialisation"
|
||||
@echo " "
|
||||
@echo These are not really examples, but tests various parts of the driver
|
||||
@echo " example.metadata Tests various metadata methods"
|
||||
@echo " example.threadsafe Tests the driver's thread safety"
|
||||
@echo ------------------------------------------------------------
|
||||
@echo
|
||||
|
||||
|
@ -170,6 +173,6 @@ example/datestyle.class: example/datestyle.java
|
|||
example/psql.class: example/psql.java
|
||||
example/ImageViewer.class: example/ImageViewer.java
|
||||
#example/Objects.class: example/Objects.java
|
||||
|
||||
example/threadsafe.class: example/threadsafe.java
|
||||
example/metadata.class: example/metadata.java
|
||||
#######################################################################
|
||||
|
|
|
@ -0,0 +1,353 @@
|
|||
package example;
|
||||
|
||||
import java.io.*;
|
||||
import java.sql.*;
|
||||
import java.text.*;
|
||||
|
||||
// rare in user code, but we use the LargeObject API in this test
|
||||
import postgresql.largeobject.*;
|
||||
|
||||
/**
|
||||
* This example tests the thread safety of the driver.
|
||||
*
|
||||
* It does this by performing several queries, in different threads. Each
|
||||
* thread has it's own Statement object, which is (in my understanding of the
|
||||
* jdbc specification) the minimum requirement.
|
||||
*
|
||||
*/
|
||||
|
||||
public class threadsafe
|
||||
{
|
||||
Connection db; // The connection to the database
|
||||
Statement st; // Our statement to run queries with
|
||||
|
||||
public threadsafe(String args[]) throws ClassNotFoundException, FileNotFoundException, IOException, SQLException
|
||||
{
|
||||
String url = args[0];
|
||||
String usr = args[1];
|
||||
String pwd = args[2];
|
||||
|
||||
// Load the driver
|
||||
Class.forName("postgresql.Driver");
|
||||
|
||||
// Connect to database
|
||||
System.out.println("Connecting to Database URL = " + url);
|
||||
db = DriverManager.getConnection(url, usr, pwd);
|
||||
|
||||
System.out.println("Connected...Now creating a statement");
|
||||
st = db.createStatement();
|
||||
|
||||
// Clean up the database (in case we failed earlier) then initialise
|
||||
cleanup();
|
||||
|
||||
// Now run tests using JDBC methods, then LargeObjects
|
||||
doexample();
|
||||
|
||||
// Clean up the database
|
||||
cleanup();
|
||||
|
||||
// Finally close the database
|
||||
System.out.println("Now closing the connection");
|
||||
st.close();
|
||||
db.close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This drops the table (if it existed). No errors are reported.
|
||||
*/
|
||||
public void cleanup()
|
||||
{
|
||||
try {
|
||||
st.executeUpdate("drop table basic");
|
||||
} catch(Exception ex) {
|
||||
// We ignore any errors here
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This performs the example
|
||||
*/
|
||||
public void doexample() throws SQLException
|
||||
{
|
||||
System.out.println("\nThis test runs three Threads. Two simply insert data into a table, then\nthey perform a query. While they are running, a third thread is running,\nand it load data into, then reads from a Large Object.\n\nIf alls well, this should run without any errors. If so, we are Thread Safe.\nWhy test JDBC & LargeObject's? Because both will run over the network\nconnection, and if locking on the stream isn't done correctly, the backend\nwill get pretty confused!\n");
|
||||
|
||||
thread3 thread3=null;
|
||||
|
||||
try {
|
||||
|
||||
// create the two threads
|
||||
Thread thread0 = Thread.currentThread();
|
||||
Thread thread1 = new thread1(db);
|
||||
Thread thread2 = new thread2(db);
|
||||
thread3 = new thread3(db);
|
||||
|
||||
// now run, and wait for them
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
thread3.start();
|
||||
|
||||
// ok, I know this is bad, but it does the trick here as our main thread
|
||||
// will yield as long as either of the children are still running
|
||||
System.out.println("Waiting for threads to run");
|
||||
while(thread1.isAlive() || thread2.isAlive() || thread3.isAlive())
|
||||
thread0.yield();
|
||||
|
||||
} finally {
|
||||
// clean up after thread3 (the finally ensures this is run even
|
||||
// if an exception is thrown inside the try { } construct)
|
||||
if(thread3 != null)
|
||||
thread3.cleanup();
|
||||
}
|
||||
|
||||
System.out.println("No Exceptions have been thrown. This is a good omen, as it means that we are\npretty much thread safe as we can get.");
|
||||
}
|
||||
|
||||
// This is the first thread. It's the same as the basic test
|
||||
class thread1 extends Thread
|
||||
{
|
||||
Connection c;
|
||||
Statement st;
|
||||
|
||||
public thread1(Connection c) throws SQLException {
|
||||
this.c = c;
|
||||
st = c.createStatement();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("Thread 1 running...");
|
||||
|
||||
// First we need a table to store data in
|
||||
st.executeUpdate("create table basic (a int2, b int2)");
|
||||
|
||||
// Now insert some data, using the Statement
|
||||
st.executeUpdate("insert into basic values (1,1)");
|
||||
st.executeUpdate("insert into basic values (2,1)");
|
||||
st.executeUpdate("insert into basic values (3,1)");
|
||||
|
||||
// For large inserts, a PreparedStatement is more efficient, because it
|
||||
// supports the idea of precompiling the SQL statement, and to store
|
||||
// directly, a Java object into any column. PostgreSQL doesnt support
|
||||
// precompiling, but does support setting a column to the value of a
|
||||
// Java object (like Date, String, etc).
|
||||
//
|
||||
// Also, this is the only way of writing dates in a datestyle independent
|
||||
// manner. (DateStyles are PostgreSQL's way of handling different methods
|
||||
// of representing dates in the Date data type.)
|
||||
PreparedStatement ps = db.prepareStatement("insert into basic values (?,?)");
|
||||
for(int i=2;i<200;i++) {
|
||||
ps.setInt(1,4); // "column a" = 5
|
||||
ps.setInt(2,i); // "column b" = i
|
||||
ps.executeUpdate(); // executeUpdate because insert returns no data
|
||||
if((i%50)==0)
|
||||
DriverManager.println("Thread 1 done "+i+" inserts");
|
||||
}
|
||||
ps.close(); // Always close when we are done with it
|
||||
|
||||
// Finally perform a query on the table
|
||||
DriverManager.println("Thread 1 performing a query");
|
||||
ResultSet rs = st.executeQuery("select a, b from basic");
|
||||
int cnt=0;
|
||||
if(rs!=null) {
|
||||
// Now we run through the result set, printing out the result.
|
||||
// Note, we must call .next() before attempting to read any results
|
||||
while(rs.next()) {
|
||||
int a = rs.getInt("a"); // This shows how to get the value by name
|
||||
int b = rs.getInt(2); // This shows how to get the value by column
|
||||
//System.out.println(" a="+a+" b="+b);
|
||||
cnt++;
|
||||
}
|
||||
rs.close(); // again, you must close the result when done
|
||||
}
|
||||
DriverManager.println("Thread 1 read "+cnt+" rows");
|
||||
|
||||
// The last thing to do is to drop the table. This is done in the
|
||||
// cleanup() method.
|
||||
System.out.println("Thread 1 finished");
|
||||
} catch(SQLException se) {
|
||||
System.err.println("Thread 1: "+se.toString());
|
||||
se.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is the second thread. It's the similar to the basic test, and thread1
|
||||
// except it works on another table.
|
||||
class thread2 extends Thread
|
||||
{
|
||||
Connection c;
|
||||
Statement st;
|
||||
|
||||
public thread2(Connection c) throws SQLException {
|
||||
this.c = c;
|
||||
st = c.createStatement();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("Thread 2 running...");
|
||||
|
||||
// For large inserts, a PreparedStatement is more efficient, because it
|
||||
// supports the idea of precompiling the SQL statement, and to store
|
||||
// directly, a Java object into any column. PostgreSQL doesnt support
|
||||
// precompiling, but does support setting a column to the value of a
|
||||
// Java object (like Date, String, etc).
|
||||
//
|
||||
// Also, this is the only way of writing dates in a datestyle independent
|
||||
// manner. (DateStyles are PostgreSQL's way of handling different methods
|
||||
// of representing dates in the Date data type.)
|
||||
PreparedStatement ps = db.prepareStatement("insert into basic values (?,?)");
|
||||
for(int i=2;i<200;i++) {
|
||||
ps.setInt(1,4); // "column a" = 5
|
||||
ps.setInt(2,i); // "column b" = i
|
||||
ps.executeUpdate(); // executeUpdate because insert returns no data
|
||||
if((i%50)==0)
|
||||
DriverManager.println("Thread 2 done "+i+" inserts");
|
||||
}
|
||||
ps.close(); // Always close when we are done with it
|
||||
|
||||
// Finally perform a query on the table
|
||||
DriverManager.println("Thread 2 performing a query");
|
||||
ResultSet rs = st.executeQuery("select * from basic where b>1");
|
||||
int cnt=0;
|
||||
if(rs!=null) {
|
||||
// First find out the column numbers.
|
||||
//
|
||||
// It's best to do this here, as calling the methods with the column
|
||||
// numbers actually performs this call each time they are called. This
|
||||
// really speeds things up on large queries.
|
||||
//
|
||||
int col_a = rs.findColumn("a");
|
||||
int col_b = rs.findColumn("b");
|
||||
|
||||
// Now we run through the result set, printing out the result.
|
||||
// Again, we must call .next() before attempting to read any results
|
||||
while(rs.next()) {
|
||||
int a = rs.getInt(col_a); // This shows how to get the value by name
|
||||
int b = rs.getInt(col_b); // This shows how to get the value by column
|
||||
//System.out.println(" a="+a+" b="+b);
|
||||
cnt++;
|
||||
}
|
||||
rs.close(); // again, you must close the result when done
|
||||
}
|
||||
DriverManager.println("Thread 2 read "+cnt+" rows");
|
||||
|
||||
// The last thing to do is to drop the table. This is done in the
|
||||
// cleanup() method.
|
||||
System.out.println("Thread 2 finished");
|
||||
} catch(SQLException se) {
|
||||
System.err.println("Thread 2: "+se.toString());
|
||||
se.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is the third thread. It loads, then reads from a LargeObject, using
|
||||
// our LargeObject api.
|
||||
//
|
||||
// The purpose of this is to test that FastPath will work in between normal
|
||||
// JDBC queries.
|
||||
class thread3 extends Thread
|
||||
{
|
||||
Connection c;
|
||||
Statement st;
|
||||
LargeObjectManager lom;
|
||||
LargeObject lo;
|
||||
int oid;
|
||||
|
||||
public thread3(Connection c) throws SQLException {
|
||||
this.c = c;
|
||||
//st = c.createStatement();
|
||||
|
||||
// create a blob
|
||||
lom = ((postgresql.Connection)c).getLargeObjectAPI();
|
||||
oid = lom.create();
|
||||
System.out.println("Thread 3 has created a blob of oid "+oid);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("Thread 3 running...");
|
||||
|
||||
DriverManager.println("Thread 3: Loading data into blob "+oid);
|
||||
lo = lom.open(oid);
|
||||
FileInputStream fis = new FileInputStream("example/threadsafe.java");
|
||||
// keep the buffer size small, to allow the other thread a chance
|
||||
byte buf[] = new byte[128];
|
||||
int rc,bc=1,bs=0;
|
||||
while((rc=fis.read(buf))>0) {
|
||||
DriverManager.println("Thread 3 read block "+bc+" "+bs+" bytes");
|
||||
lo.write(buf,0,rc);
|
||||
bc++;
|
||||
bs+=rc;
|
||||
}
|
||||
lo.close();
|
||||
fis.close();
|
||||
|
||||
DriverManager.println("Thread 3: Reading blob "+oid);
|
||||
lo=lom.open(oid);
|
||||
bc=0;
|
||||
while(buf.length>0) {
|
||||
buf=lo.read(buf.length);
|
||||
if(buf.length>0) {
|
||||
String s = new String(buf);
|
||||
bc++;
|
||||
DriverManager.println("Thread 3 block "+bc);
|
||||
DriverManager.println("Block "+bc+" got "+s);
|
||||
}
|
||||
}
|
||||
lo.close();
|
||||
|
||||
System.out.println("Thread 3 finished");
|
||||
} catch(Exception se) {
|
||||
System.err.println("Thread 3: "+se.toString());
|
||||
se.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() throws SQLException {
|
||||
if(lom!=null && oid!=0) {
|
||||
System.out.println("Thread 3: Removing blob oid="+oid);
|
||||
lom.delete(oid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display some instructions on how to run the example
|
||||
*/
|
||||
public static void instructions()
|
||||
{
|
||||
System.out.println("\nThis tests the thread safety of the driver.\n\nThis is done in two parts, the first with standard JDBC calls, and the\nsecond mixing FastPath and LargeObject calls with queries.\n");
|
||||
System.out.println("Useage:\n java example.threadsafe jdbc:postgresql:database user password [debug]\n\nThe debug field can be anything. It's presence will enable DriverManager's\ndebug trace. Unless you want to see screens of items, don't put anything in\nhere.");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This little lot starts the test
|
||||
*/
|
||||
public static void main(String args[])
|
||||
{
|
||||
System.out.println("PostgreSQL Thread Safety test v6.4 rev 1\n");
|
||||
|
||||
if(args.length<3)
|
||||
instructions();
|
||||
|
||||
// This line outputs debug information to stderr. To enable this, simply
|
||||
// add an extra parameter to the command line
|
||||
if(args.length>3)
|
||||
DriverManager.setLogStream(System.err);
|
||||
|
||||
// Now run the tests
|
||||
try {
|
||||
threadsafe test = new threadsafe(args);
|
||||
} catch(Exception ex) {
|
||||
System.err.println("Exception caught.\n"+ex);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -635,8 +635,11 @@ public class Connection implements java.sql.Connection
|
|||
* @return a ResultSet holding the results
|
||||
* @exception SQLException if a database error occurs
|
||||
*/
|
||||
public synchronized ResultSet ExecSQL(String sql) throws SQLException
|
||||
public ResultSet ExecSQL(String sql) throws SQLException
|
||||
{
|
||||
// added Oct 7 1998 to give us thread safety.
|
||||
synchronized(pg_stream) {
|
||||
|
||||
Field[] fields = null;
|
||||
Vector tuples = new Vector();
|
||||
byte[] buf = new byte[sql.length()];
|
||||
|
@ -737,6 +740,7 @@ public class Connection implements java.sql.Connection
|
|||
if (final_error != null)
|
||||
throw final_error;
|
||||
return new ResultSet(this, fields, tuples, recv_status, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -68,6 +68,9 @@ public class Fastpath
|
|||
*/
|
||||
public Object fastpath(int fnid,boolean resulttype,FastpathArg[] args) throws SQLException
|
||||
{
|
||||
// added Oct 7 1998 to give us thread safety
|
||||
synchronized(stream) {
|
||||
|
||||
// send the function call
|
||||
try {
|
||||
// 70 is 'F' in ASCII. Note: don't use SendChar() here as it adds padding
|
||||
|
@ -153,6 +156,7 @@ public class Fastpath
|
|||
throw new SQLException("Fastpath: protocol error. Got '"+((char)in)+"'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue