diff --git a/src/interfaces/jdbc/Makefile b/src/interfaces/jdbc/Makefile
index c7a0227ddc46d331535133c291717abe9f923412..bda001e8b0ea03698628aef771db40aab93cf85b 100644
--- a/src/interfaces/jdbc/Makefile
+++ b/src/interfaces/jdbc/Makefile
@@ -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
 #######################################################################
diff --git a/src/interfaces/jdbc/example/threadsafe.java b/src/interfaces/jdbc/example/threadsafe.java
new file mode 100644
index 0000000000000000000000000000000000000000..48dff108f13a4fc0c0d879b1794ae672865b527c
--- /dev/null
+++ b/src/interfaces/jdbc/example/threadsafe.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();
+    }
+  }
+}
diff --git a/src/interfaces/jdbc/postgresql/Connection.java b/src/interfaces/jdbc/postgresql/Connection.java
index 31f6e11980eef03bc0aa8a5e1db32e4109ccc136..be15b38abe0979fa2c1e717696662061755f8d3a 100644
--- a/src/interfaces/jdbc/postgresql/Connection.java
+++ b/src/interfaces/jdbc/postgresql/Connection.java
@@ -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);
+    }
   }
   
   /**
diff --git a/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java b/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java
index fdb686865541ab233c161e282576b5d221cc7c02..ab702f457f2d7c0e91baa72ebbd401e418b203b5 100644
--- a/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java
+++ b/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java
@@ -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)+"'");
 	}
     }
+    }
   }
   
   /**