package org.postgresql.jdbc2;

// IMPORTANT NOTE: This file implements the JDBC 2 version of the driver.
// If you make any modifications to this file, you must make sure that the
// changes are also made (if relevent) to the related JDBC 1 class in the
// org.postgresql.jdbc1 package.

import java.io.*;
import java.math.*;
import java.sql.*;
import java.text.*;
import java.util.*;
import org.postgresql.largeobject.*;
import org.postgresql.util.*;

/**
 * A SQL Statement is pre-compiled and stored in a PreparedStatement object.
 * This object can then be used to efficiently execute this statement multiple
 * times.
 *
 * <p><B>Note:</B> The setXXX methods for setting IN parameter values must
 * specify types that are compatible with the defined SQL type of the input
 * parameter.  For instance, if the IN parameter has SQL type Integer, then
 * setInt should be used.
 *
 * <p>If arbitrary parameter type conversions are required, then the setObject
 * method should be used with a target SQL type.
 *
 * @see ResultSet
 * @see java.sql.PreparedStatement
 */
public class PreparedStatement extends Statement implements java.sql.PreparedStatement
{
	String sql;
	String[] templateStrings;
	String[] inStrings;
	Connection connection;

	// Some performance caches
	private StringBuffer sbuf = new StringBuffer();

	// We use ThreadLocal for SimpleDateFormat's because they are not that
	// thread safe, so each calling thread has its own object.
	private static ThreadLocal tl_df = new ThreadLocal(); // setDate() SimpleDateFormat
	private static ThreadLocal tl_tsdf = new ThreadLocal(); // setTimestamp() SimpleDateFormat

	/**
	 * Constructor for the PreparedStatement class.
	 * Split the SQL statement into segments - separated by the arguments.
	 * When we rebuild the thing with the arguments, we can substitute the
	 * args and join the whole thing together.
	 *
	 * @param conn the instanatiating connection
	 * @param sql the SQL statement with ? for IN markers
	 * @exception SQLException if something bad occurs
	 */
	public PreparedStatement(Connection connection, String sql) throws SQLException
	{
		super(connection);

		Vector v = new Vector();
		boolean inQuotes = false;
		int lastParmEnd = 0, i;

		this.sql = sql;
		this.connection = connection;

		for (i = 0; i < sql.length(); ++i)
		{
			int c = sql.charAt(i);

			if (c == '\'')
				inQuotes = !inQuotes;
			if (c == '?' && !inQuotes)
			{
				v.addElement(sql.substring (lastParmEnd, i));
				lastParmEnd = i + 1;
			}
		}
		v.addElement(sql.substring (lastParmEnd, sql.length()));

		templateStrings = new String[v.size()];
		inStrings = new String[v.size() - 1];
		clearParameters();

		for (i = 0 ; i < templateStrings.length; ++i)
			templateStrings[i] = (String)v.elementAt(i);
	}

	/**
	 * A Prepared SQL query is executed and its ResultSet is returned
	 *
	 * @return a ResultSet that contains the data produced by the
			*			  *			query - never null
	 * @exception SQLException if a database access error occurs
	 */
	public java.sql.ResultSet executeQuery() throws SQLException
	{
		return super.executeQuery(compileQuery());	// in Statement class
	}

	/**
	 * Execute a SQL INSERT, UPDATE or DELETE statement.  In addition,
	 * SQL statements that return nothing such as SQL DDL statements can
	 * be executed.
	 *
	 * @return either the row count for INSERT, UPDATE or DELETE; or
			*			  *			0 for SQL statements that return nothing.
	 * @exception SQLException if a database access error occurs
	 */
	public int executeUpdate() throws SQLException
	{
		return super.executeUpdate(compileQuery());		// in Statement class
	}

	/**
	 * Helper - this compiles the SQL query from the various parameters
	 * This is identical to toString() except it throws an exception if a
	 * parameter is unused.
	 */
	private synchronized String compileQuery()
	throws SQLException
	{
		sbuf.setLength(0);
		int i;

		for (i = 0 ; i < inStrings.length ; ++i)
		{
			if (inStrings[i] == null)
				throw new PSQLException("postgresql.prep.param", new Integer(i + 1));
			sbuf.append (templateStrings[i]).append (inStrings[i]);
		}
		sbuf.append(templateStrings[inStrings.length]);
		return sbuf.toString();
	}

	/**
	 * Set a parameter to SQL NULL
	 *
	 * <p><B>Note:</B> You must specify the parameters SQL type (although
	 * PostgreSQL ignores it)
	 *
	 * @param parameterIndex the first parameter is 1, etc...
	 * @param sqlType the SQL type code defined in java.sql.Types
	 * @exception SQLException if a database access error occurs
	 */
	public void setNull(int parameterIndex, int sqlType) throws SQLException
	{
		set(parameterIndex, "null");
	}

	/**
	 * Set a parameter to a Java boolean value.  The driver converts this
	 * to a SQL BIT value when it sends it to the database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setBoolean(int parameterIndex, boolean x) throws SQLException
	{
		set(parameterIndex, x ? "'t'" : "'f'");
	}

	/**
	 * Set a parameter to a Java byte value.  The driver converts this to
	 * a SQL TINYINT value when it sends it to the database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setByte(int parameterIndex, byte x) throws SQLException
	{
		set(parameterIndex, Integer.toString(x));
	}

	/**
	 * Set a parameter to a Java short value.  The driver converts this
	 * to a SQL SMALLINT value when it sends it to the database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setShort(int parameterIndex, short x) throws SQLException
	{
		set(parameterIndex, Integer.toString(x));
	}

	/**
	 * Set a parameter to a Java int value.  The driver converts this to
	 * a SQL INTEGER value when it sends it to the database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setInt(int parameterIndex, int x) throws SQLException
	{
		set(parameterIndex, Integer.toString(x));
	}

	/**
	 * Set a parameter to a Java long value.  The driver converts this to
	 * a SQL BIGINT value when it sends it to the database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setLong(int parameterIndex, long x) throws SQLException
	{
		set(parameterIndex, Long.toString(x));
	}

	/**
	 * Set a parameter to a Java float value.  The driver converts this
	 * to a SQL FLOAT value when it sends it to the database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setFloat(int parameterIndex, float x) throws SQLException
	{
		set(parameterIndex, Float.toString(x));
	}

	/**
	 * Set a parameter to a Java double value.	The driver converts this
	 * to a SQL DOUBLE value when it sends it to the database
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setDouble(int parameterIndex, double x) throws SQLException
	{
		set(parameterIndex, Double.toString(x));
	}

	/**
	 * Set a parameter to a java.lang.BigDecimal value.  The driver
	 * converts this to a SQL NUMERIC value when it sends it to the
	 * database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException
	{
		set(parameterIndex, x.toString());
	}

	/**
	 * Set a parameter to a Java String value.	The driver converts this
	 * to a SQL VARCHAR or LONGVARCHAR value (depending on the arguments
	 * size relative to the driver's limits on VARCHARs) when it sends it
	 * to the database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setString(int parameterIndex, String x) throws SQLException
	{
		// if the passed string is null, then set this column to null
		if (x == null)
			setNull(parameterIndex, Types.OTHER);
		else
		{
			// use the shared buffer object. Should never clash but this makes
			// us thread safe!
			synchronized (sbuf)
			{
				sbuf.setLength(0);
				int i;

				sbuf.append('\'');
				for (i = 0 ; i < x.length() ; ++i)
				{
					char c = x.charAt(i);
					if (c == '\\' || c == '\'')
						sbuf.append((char)'\\');
					sbuf.append(c);
				}
				sbuf.append('\'');
				set(parameterIndex, sbuf.toString());
			}
		}
	}

	/**
	 * Set a parameter to a Java array of bytes.  The driver converts this
	 * to a SQL VARBINARY or LONGVARBINARY (depending on the argument's
	 * size relative to the driver's limits on VARBINARYs) when it sends
	 * it to the database.
	 *
	 * <p>Implementation note:
	 * <br>With org.postgresql, this creates a large object, and stores the
	 * objects oid in this column.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setBytes(int parameterIndex, byte x[]) throws SQLException
	{
		if (connection.haveMinimumCompatibleVersion("7.2"))
		{
			//Version 7.2 supports the bytea datatype for byte arrays
			if (null == x)
			{
				setNull(parameterIndex, Types.OTHER);
			}
			else
			{
				setString(parameterIndex, PGbytea.toPGString(x));
			}
		}
		else
		{
			//Version 7.1 and earlier support done as LargeObjects
			LargeObjectManager lom = connection.getLargeObjectAPI();
			int oid = lom.create();
			LargeObject lob = lom.open(oid);
			lob.write(x);
			lob.close();
			setInt(parameterIndex, oid);
		}
	}

	/**
	 * Set a parameter to a java.sql.Date value.  The driver converts this
	 * to a SQL DATE value when it sends it to the database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
	{
		if (null == x)
		{
			setNull(parameterIndex, Types.OTHER);
		}
		else
		{
			SimpleDateFormat df = (SimpleDateFormat) tl_df.get();
			if (df == null)
			{
				df = new SimpleDateFormat("''yyyy-MM-dd''");
				tl_df.set(df);
			}
			set(parameterIndex, df.format(x));
		}
		// The above is how the date should be handled.
		//
		// However, in JDK's prior to 1.1.6 (confirmed with the
		// Linux jdk1.1.3 and the Win95 JRE1.1.5), SimpleDateFormat seems
		// to format a date to the previous day. So the fix is to add a day
		// before formatting.
		//
		// PS: 86400000 is one day
		//
		//set(parameterIndex, df.format(new java.util.Date(x.getTime()+86400000)));
	}

	/**
	 * Set a parameter to a java.sql.Time value.  The driver converts
	 * this to a SQL TIME value when it sends it to the database.
	 *
	 * @param parameterIndex the first parameter is 1...));
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setTime(int parameterIndex, Time x) throws SQLException
	{
		if (null == x)
		{
			setNull(parameterIndex, Types.OTHER);
		}
		else
		{
			set(parameterIndex, "'" + x.toString() + "'");
		}
	}

	/**
	 * Set a parameter to a java.sql.Timestamp value.  The driver converts
	 * this to a SQL TIMESTAMP value when it sends it to the database.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
	{
		if (null == x)
		{
			setNull(parameterIndex, Types.OTHER);
		}
		else
		{
			SimpleDateFormat df = (SimpleDateFormat) tl_tsdf.get();
			if (df == null)
			{
				df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
				df.setTimeZone(TimeZone.getTimeZone("GMT"));
				tl_tsdf.set(df);
			}

			// Use the shared StringBuffer
			synchronized (sbuf)
			{
				sbuf.setLength(0);
				sbuf.append("'").append(df.format(x)).append('.').append(x.getNanos() / 10000000).append("+00'");
				set(parameterIndex, sbuf.toString());
			}

			// The above works, but so does the following. I'm leaving the above in, but this seems
			// to be identical. Pays to read the docs ;-)
			//set(parameterIndex,"'"+x.toString()+"'");
		}
	}

	/**
	 * When a very large ASCII value is input to a LONGVARCHAR parameter,
	 * it may be more practical to send it via a java.io.InputStream.
	 * JDBC will read the data from the stream as needed, until it reaches
	 * end-of-file.  The JDBC driver will do any necessary conversion from
	 * ASCII to the database char format.
	 *
	 * <P><B>Note:</B> This stream object can either be a standard Java
	 * stream object or your own subclass that implements the standard
	 * interface.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @param length the number of bytes in the stream
	 * @exception SQLException if a database access error occurs
	 */
	public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException
	{
		if (connection.haveMinimumCompatibleVersion("7.2"))
		{
			//Version 7.2 supports AsciiStream for all PG text types (char, varchar, text)
			//As the spec/javadoc for this method indicate this is to be used for
			//large String values (i.e. LONGVARCHAR)  PG doesn't have a separate
			//long varchar datatype, but with toast all text datatypes are capable of
			//handling very large values.  Thus the implementation ends up calling
			//setString() since there is no current way to stream the value to the server
			try
			{
				InputStreamReader l_inStream = new InputStreamReader(x, "ASCII");
				char[] l_chars = new char[length];
				int l_charsRead = l_inStream.read(l_chars, 0, length);
				setString(parameterIndex, new String(l_chars, 0, l_charsRead));
			}
			catch (UnsupportedEncodingException l_uee)
			{
				throw new PSQLException("postgresql.unusual", l_uee);
			}
			catch (IOException l_ioe)
			{
				throw new PSQLException("postgresql.unusual", l_ioe);
			}
		}
		else
		{
			//Version 7.1 supported only LargeObjects by treating everything
			//as binary data
			setBinaryStream(parameterIndex, x, length);
		}
	}

	/**
	 * When a very large Unicode value is input to a LONGVARCHAR parameter,
	 * it may be more practical to send it via a java.io.InputStream.
	 * JDBC will read the data from the stream as needed, until it reaches
	 * end-of-file.  The JDBC driver will do any necessary conversion from
	 * UNICODE to the database char format.
	 *
	 * ** DEPRECIATED IN JDBC 2 **
	 *
	 * <P><B>Note:</B> This stream object can either be a standard Java
	 * stream object or your own subclass that implements the standard
	 * interface.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 * @deprecated
	 */
	public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException
	{
		if (connection.haveMinimumCompatibleVersion("7.2"))
		{
			//Version 7.2 supports AsciiStream for all PG text types (char, varchar, text)
			//As the spec/javadoc for this method indicate this is to be used for
			//large String values (i.e. LONGVARCHAR)  PG doesn't have a separate
			//long varchar datatype, but with toast all text datatypes are capable of
			//handling very large values.  Thus the implementation ends up calling
			//setString() since there is no current way to stream the value to the server
			try
			{
				InputStreamReader l_inStream = new InputStreamReader(x, "UTF-8");
				char[] l_chars = new char[length];
				int l_charsRead = l_inStream.read(l_chars, 0, length);
				setString(parameterIndex, new String(l_chars, 0, l_charsRead));
			}
			catch (UnsupportedEncodingException l_uee)
			{
				throw new PSQLException("postgresql.unusual", l_uee);
			}
			catch (IOException l_ioe)
			{
				throw new PSQLException("postgresql.unusual", l_ioe);
			}
		}
		else
		{
			//Version 7.1 supported only LargeObjects by treating everything
			//as binary data
			setBinaryStream(parameterIndex, x, length);
		}
	}

	/**
	 * When a very large binary value is input to a LONGVARBINARY parameter,
	 * it may be more practical to send it via a java.io.InputStream.
	 * JDBC will read the data from the stream as needed, until it reaches
	 * end-of-file.
	 *
	 * <P><B>Note:</B> This stream object can either be a standard Java
	 * stream object or your own subclass that implements the standard
	 * interface.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the parameter value
	 * @exception SQLException if a database access error occurs
	 */
	public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException
	{
		if (connection.haveMinimumCompatibleVersion("7.2"))
		{
			//Version 7.2 supports BinaryStream for for the PG bytea type
			//As the spec/javadoc for this method indicate this is to be used for
			//large binary values (i.e. LONGVARBINARY)	PG doesn't have a separate
			//long binary datatype, but with toast the bytea datatype is capable of
			//handling very large values.  Thus the implementation ends up calling
			//setBytes() since there is no current way to stream the value to the server
			byte[] l_bytes = new byte[length];
			int l_bytesRead;
			try
			{
				l_bytesRead = x.read(l_bytes, 0, length);
			}
			catch (IOException l_ioe)
			{
				throw new PSQLException("postgresql.unusual", l_ioe);
			}
			if (l_bytesRead == length)
			{
				setBytes(parameterIndex, l_bytes);
			}
			else
			{
				//the stream contained less data than they said
				byte[] l_bytes2 = new byte[l_bytesRead];
				System.arraycopy(l_bytes, 0, l_bytes2, 0, l_bytesRead);
				setBytes(parameterIndex, l_bytes2);
			}
		}
		else
		{
			//Version 7.1 only supported streams for LargeObjects
			//but the jdbc spec indicates that streams should be
			//available for LONGVARBINARY instead
			LargeObjectManager lom = connection.getLargeObjectAPI();
			int oid = lom.create();
			LargeObject lob = lom.open(oid);
			OutputStream los = lob.getOutputStream();
			try
			{
				// could be buffered, but then the OutputStream returned by LargeObject
				// is buffered internally anyhow, so there would be no performance
				// boost gained, if anything it would be worse!
				int c = x.read();
				int p = 0;
				while (c > -1 && p < length)
				{
					los.write(c);
					c = x.read();
					p++;
				}
				los.close();
			}
			catch (IOException se)
			{
				throw new PSQLException("postgresql.unusual", se);
			}
			// lob is closed by the stream so don't call lob.close()
			setInt(parameterIndex, oid);
		}
	}

	/**
	 * In general, parameter values remain in force for repeated used of a
	 * Statement.  Setting a parameter value automatically clears its
	 * previous value.	However, in coms cases, it is useful to immediately
	 * release the resources used by the current parameter values; this
	 * can be done by calling clearParameters
	 *
	 * @exception SQLException if a database access error occurs
	 */
	public void clearParameters() throws SQLException
	{
		int i;

		for (i = 0 ; i < inStrings.length ; i++)
			inStrings[i] = null;
	}

	/**
	 * Set the value of a parameter using an object; use the java.lang
	 * equivalent objects for integral values.
	 *
	 * <P>The given Java object will be converted to the targetSqlType before
	 * being sent to the database.
	 *
	 * <P>note that this method may be used to pass database-specific
	 * abstract data types.  This is done by using a Driver-specific
	 * Java type and using a targetSqlType of java.sql.Types.OTHER
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the object containing the input parameter value
	 * @param targetSqlType The SQL type to be send to the database
	 * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC
	 *	types this is the number of digits after the decimal.  For
	 *	all other types this value will be ignored.
	 * @exception SQLException if a database access error occurs
	 */
	public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) throws SQLException
	{
		if (x == null)
		{
			setNull(parameterIndex, Types.OTHER);
			return ;
		}
		switch (targetSqlType)
		{
		case Types.TINYINT:
		case Types.SMALLINT:
		case Types.INTEGER:
		case Types.BIGINT:
		case Types.REAL:
		case Types.FLOAT:
		case Types.DOUBLE:
		case Types.DECIMAL:
		case Types.NUMERIC:
			if (x instanceof Boolean)
				set(parameterIndex, ((Boolean)x).booleanValue() ? "1" : "0");
			else
				set(parameterIndex, x.toString());
			break;
		case Types.CHAR:
		case Types.VARCHAR:
		case Types.LONGVARCHAR:
			setString(parameterIndex, x.toString());
			break;
		case Types.DATE:
			setDate(parameterIndex, (java.sql.Date)x);
			break;
		case Types.TIME:
			setTime(parameterIndex, (Time)x);
			break;
		case Types.TIMESTAMP:
			setTimestamp(parameterIndex, (Timestamp)x);
			break;
		case Types.BIT:
			if (x instanceof Boolean)
			{
				set(parameterIndex, ((Boolean)x).booleanValue() ? "TRUE" : "FALSE");
			}
			else
			{
				throw new PSQLException("postgresql.prep.type");
			}
			break;
		case Types.BINARY:
		case Types.VARBINARY:
			setObject(parameterIndex, x);
			break;
		case Types.OTHER:
			setString(parameterIndex, ((PGobject)x).getValue());
			break;
		default:
			throw new PSQLException("postgresql.prep.type");
		}
	}

	public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException
	{
		setObject(parameterIndex, x, targetSqlType, 0);
	}

	/**
	 * This stores an Object into a parameter.
	 * <p>New for 6.4, if the object is not recognised, but it is
	 * Serializable, then the object is serialised using the
	 * org.postgresql.util.Serialize class.
	 */
	public void setObject(int parameterIndex, Object x) throws SQLException
	{
		if (x == null)
		{
			setNull(parameterIndex, Types.OTHER);
			return ;
		}
		if (x instanceof String)
			setString(parameterIndex, (String)x);
		else if (x instanceof BigDecimal)
			setBigDecimal(parameterIndex, (BigDecimal)x);
		else if (x instanceof Short)
			setShort(parameterIndex, ((Short)x).shortValue());
		else if (x instanceof Integer)
			setInt(parameterIndex, ((Integer)x).intValue());
		else if (x instanceof Long)
			setLong(parameterIndex, ((Long)x).longValue());
		else if (x instanceof Float)
			setFloat(parameterIndex, ((Float)x).floatValue());
		else if (x instanceof Double)
			setDouble(parameterIndex, ((Double)x).doubleValue());
		else if (x instanceof byte[])
			setBytes(parameterIndex, (byte[])x);
		else if (x instanceof java.sql.Date)
			setDate(parameterIndex, (java.sql.Date)x);
		else if (x instanceof Time)
			setTime(parameterIndex, (Time)x);
		else if (x instanceof Timestamp)
			setTimestamp(parameterIndex, (Timestamp)x);
		else if (x instanceof Boolean)
			setBoolean(parameterIndex, ((Boolean)x).booleanValue());
		else if (x instanceof PGobject)
			setString(parameterIndex, ((PGobject)x).getValue());
		else
			// Try to store java object in database
			setSerialize(parameterIndex, connection.putObject(x), x.getClass().getName() );
	}

	/**
	 * Some prepared statements return multiple results; the execute method
	 * handles these complex statements as well as the simpler form of
	 * statements handled by executeQuery and executeUpdate
	 *
	 * @return true if the next result is a ResultSet; false if it is an
	 *	update count or there are no more results
	 * @exception SQLException if a database access error occurs
	 */
	public boolean execute() throws SQLException
	{
		return super.execute(compileQuery());	// in Statement class
	}

	/**
	 * Returns the SQL statement with the current template values
	 * substituted.
			* NB: This is identical to compileQuery() except instead of throwing
			* SQLException if a parameter is null, it places ? instead.
	 */
	public String toString()
	{
		synchronized (sbuf)
		{
			sbuf.setLength(0);
			int i;

			for (i = 0 ; i < inStrings.length ; ++i)
			{
				if (inStrings[i] == null)
					sbuf.append( '?' );
				else
					sbuf.append (templateStrings[i]);
				sbuf.append (inStrings[i]);
			}
			sbuf.append(templateStrings[inStrings.length]);
			return sbuf.toString();
		}
	}

	// **************************************************************
	//	END OF PUBLIC INTERFACE
	// **************************************************************

	/**
	 * There are a lot of setXXX classes which all basically do
	 * the same thing.	We need a method which actually does the
	 * set for us.
	 *
	 * @param paramIndex the index into the inString
	 * @param s a string to be stored
	 * @exception SQLException if something goes wrong
	 */
	private void set(int paramIndex, String s) throws SQLException
	{
		if (paramIndex < 1 || paramIndex > inStrings.length)
			throw new PSQLException("postgresql.prep.range");
		inStrings[paramIndex - 1] = s;
	}

	/**
	 * Set a parameter to a tablerow-type oid reference.
	 *
	 * @param parameterIndex the first parameter is 1...
	 * @param x the oid of the object from org.postgresql.util.Serialize.store
	 * @param classname the classname of the java object x
	 * @exception SQLException if a database access error occurs
	 */
	private void setSerialize(int parameterIndex, long x, String classname) throws SQLException
	{
		// converts . to _, toLowerCase, and ensures length<32
		String tablename = Serialize.toPostgreSQL( classname );
		DriverManager.println("setSerialize: setting " + x + "::" + tablename );

		// OID reference to tablerow-type must be cast like:  <oid>::<tablename>
		// Note that postgres support for tablerow data types is incomplete/broken.
		// This cannot be just a plain OID because then there would be ambiguity
		// between when you want the oid itself and when you want the object
		// an oid references.
		set(parameterIndex, Long.toString(x) + "::" + tablename );
	}


	// ** JDBC 2 Extensions **

	/**
	 * This parses the query and adds it to the current batch
	 */
	public void addBatch() throws SQLException
	{
		super.addBatch(compileQuery());
	}

	/**
	 * Not sure what this one does, so I'm saying this returns the MetaData for
	 * the last ResultSet returned!
	 */
	public java.sql.ResultSetMetaData getMetaData() throws SQLException
	{
		java.sql.ResultSet rs = getResultSet();
		if (rs != null)
			return rs.getMetaData();

		// Does anyone really know what this method does?
		return null;
	}

	public void setArray(int i, java.sql.Array x) throws SQLException
	{
		setString(i, x.toString());
	}

	/**
	 * Sets a Blob
	 */
	public void setBlob(int i, Blob x) throws SQLException
	{
		InputStream l_inStream = x.getBinaryStream();
		int l_length = (int) x.length();
		LargeObjectManager lom = connection.getLargeObjectAPI();
		int oid = lom.create();
		LargeObject lob = lom.open(oid);
		OutputStream los = lob.getOutputStream();
		try
		{
			// could be buffered, but then the OutputStream returned by LargeObject
			// is buffered internally anyhow, so there would be no performance
			// boost gained, if anything it would be worse!
			int c = l_inStream.read();
			int p = 0;
			while (c > -1 && p < l_length)
			{
				los.write(c);
				c = l_inStream.read();
				p++;
			}
			los.close();
		}
		catch (IOException se)
		{
			throw new PSQLException("postgresql.unusual", se);
		}
		// lob is closed by the stream so don't call lob.close()
		setInt(i, oid);
	}

	/**
	 * This is similar to setBinaryStream except it uses a Reader instead of
	 * InputStream.
	 */
	public void setCharacterStream(int i, java.io.Reader x, int length) throws SQLException
	{
		if (connection.haveMinimumCompatibleVersion("7.2"))
		{
			//Version 7.2 supports CharacterStream for for the PG text types
			//As the spec/javadoc for this method indicate this is to be used for
			//large text values (i.e. LONGVARCHAR)	PG doesn't have a separate
			//long varchar datatype, but with toast all the text datatypes are capable of
			//handling very large values.  Thus the implementation ends up calling
			//setString() since there is no current way to stream the value to the server
			char[] l_chars = new char[length];
			int l_charsRead;
			try
			{
				l_charsRead = x.read(l_chars, 0, length);
			}
			catch (IOException l_ioe)
			{
				throw new PSQLException("postgresql.unusual", l_ioe);
			}
			setString(i, new String(l_chars, 0, l_charsRead));
		}
		else
		{
			//Version 7.1 only supported streams for LargeObjects
			//but the jdbc spec indicates that streams should be
			//available for LONGVARCHAR instead
			LargeObjectManager lom = connection.getLargeObjectAPI();
			int oid = lom.create();
			LargeObject lob = lom.open(oid);
			OutputStream los = lob.getOutputStream();
			try
			{
				// could be buffered, but then the OutputStream returned by LargeObject
				// is buffered internally anyhow, so there would be no performance
				// boost gained, if anything it would be worse!
				int c = x.read();
				int p = 0;
				while (c > -1 && p < length)
				{
					los.write(c);
					c = x.read();
					p++;
				}
				los.close();
			}
			catch (IOException se)
			{
				throw new PSQLException("postgresql.unusual", se);
			}
			// lob is closed by the stream so don't call lob.close()
			setInt(i, oid);
		}
	}

	/**
	 * New in 7.1
	 */
	public void setClob(int i, Clob x) throws SQLException
	{
		InputStream l_inStream = x.getAsciiStream();
		int l_length = (int) x.length();
		LargeObjectManager lom = connection.getLargeObjectAPI();
		int oid = lom.create();
		LargeObject lob = lom.open(oid);
		OutputStream los = lob.getOutputStream();
		try
		{
			// could be buffered, but then the OutputStream returned by LargeObject
			// is buffered internally anyhow, so there would be no performance
			// boost gained, if anything it would be worse!
			int c = l_inStream.read();
			int p = 0;
			while (c > -1 && p < l_length)
			{
				los.write(c);
				c = l_inStream.read();
				p++;
			}
			los.close();
		}
		catch (IOException se)
		{
			throw new PSQLException("postgresql.unusual", se);
		}
		// lob is closed by the stream so don't call lob.close()
		setInt(i, oid);
	}

	/**
	 * At least this works as in PostgreSQL null represents anything null ;-)
	 *
	 * New in 7,1
	 */
	public void setNull(int i, int t, String s) throws SQLException
	{
		setNull(i, t);
	}

	public void setRef(int i, Ref x) throws SQLException
	{
		throw org.postgresql.Driver.notImplemented();
	}

	/**
	 * New in 7,1
	 */
	public void setDate(int i, java.sql.Date d, java.util.Calendar cal) throws SQLException
	{
		if (cal == null)
			setDate(i, d);
		else
		{
			cal.setTime(d);
			setDate(i, new java.sql.Date(cal.getTime().getTime()));
		}
	}

	/**
	 * New in 7,1
	 */
	public void setTime(int i, Time t, java.util.Calendar cal) throws SQLException
	{
		if (cal == null)
			setTime(i, t);
		else
		{
			cal.setTime(t);
			setTime(i, new java.sql.Time(cal.getTime().getTime()));
		}
	}

	/**
	 * New in 7,1
	 */
	public void setTimestamp(int i, Timestamp t, java.util.Calendar cal) throws SQLException
	{
		if (cal == null)
			setTimestamp(i, t);
		else
		{
			cal.setTime(t);
			setTimestamp(i, new java.sql.Timestamp(cal.getTime().getTime()));
		}
	}

}

