package example;

import java.io.*;
import java.sql.*;

// rare in user code, but we use the LargeObject API in this test
import org.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("org.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();

		// Because we use LargeObjects, we must use Transactions
		db.setAutoCommit(false);

		// 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 basic1");
		}
		catch (Exception ex)
		{
			// We ignore any errors here
		}

		try
		{
			st.executeUpdate("drop table basic2");
		}
		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())
				Thread.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 basic1 (a int2, b int2)");

				// Now insert some data, using the Statement
				st.executeUpdate("insert into basic1 values (1,1)");
				st.executeUpdate("insert into basic1 values (2,1)");
				st.executeUpdate("insert into basic1 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 basic1 values (?,?)");
				for (int i = 2;i < 2000;i++)
				{
					ps.setInt(1, 4);		// "column a" = 5
					ps.setInt(2, i);		// "column b" = i
					ps.executeUpdate(); // executeUpdate because insert returns no data
					//	  c.commit();
					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 basic1");
				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...");

				// First we need a table to store data in
				st.executeUpdate("create table basic2 (a int2, b int2)");

				// 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 basic2 values (?,?)");
				for (int i = 2;i < 2000;i++)
				{
					ps.setInt(1, 4);		// "column a" = 5
					ps.setInt(2, i);		// "column b" = i
					ps.executeUpdate(); // executeUpdate because insert returns no data
					//	  c.commit();
					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 basic2 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 = ((org.postgresql.PGConnection)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();
		}
	}
}
