Skip to content
Snippets Groups Projects
Commit 25b5faa7 authored by Bruce Momjian's avatar Bruce Momjian
Browse files

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
parent 9042e9d7
No related branches found
No related tags found
No related merge requests found
......@@ -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
#######################################################################
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()];
......@@ -738,6 +741,7 @@ public class Connection implements java.sql.Connection
throw final_error;
return new ResultSet(this, fields, tuples, recv_status, 1);
}
}
/**
* Receive the field descriptions from the back end
......
......@@ -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
......@@ -154,6 +157,7 @@ public class Fastpath
}
}
}
}
/**
* Send a function call to the PostgreSQL backend by name.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment