package org.postgresql;

import java.sql.*;
import org.postgresql.util.PSQLException;

/*
 * This class defines methods implemented by the two subclasses
 * org.postgresql.jdbc1.Statement and org.postgresql.jdbc2.Statement that are
 * unique to PostgreSQL's JDBC driver.
 *
 * <p>They are defined so that client code can cast to org.postgresql.Statement
 * without having to predetermine the jdbc driver type.
 *
 * <p>ie: Before this class existed, you had to use:
 *
 * <p>((org.postgresql.jdbc2.Statement)stat).getInsertedOID();
 *
 * <p>now you use:
 *
 * <p>((org.postgresql.Statement)stat).getInsertedOID();
 *
 * <p>As you can see, this is independent of JDBC1.2, JDBC2.0 or the upcoming
 * JDBC3.
 */

public abstract class Statement
{

	/** The warnings chain. */
	protected SQLWarning warnings = null;

	/** The current results */
	protected java.sql.ResultSet result = null;

	/** Maximum number of rows to return, 0 = unlimited */
	protected int maxrows = 0;

	/** Timeout (in seconds) for a query (not used) */
	protected int timeout = 0;

	protected boolean escapeProcessing = true;

	// Static variables for parsing SQL when escapeProcessing is true.
	private static final short IN_SQLCODE = 0;
	private static final short IN_STRING = 1;
	private static final short BACKSLASH = 2;
	private static final short ESC_TIMEDATE = 3;

	public Statement()
	{}

	/*
	 * Returns the status message from the current Result.<p>
	 * This is used internally by the driver.
	 *
	 * @return status message from backend
	 */
	public String getResultStatusString()
	{
		if (result == null)
			return null;
		return ((org.postgresql.ResultSet) result).getStatusString();
	}

	/*
	 * The maxRows limit is set to limit the number of rows that
	 * any ResultSet can contain.  If the limit is exceeded, the
	 * excess rows are silently dropped.
	 *
	 * @return the current maximum row limit; zero means unlimited
	 * @exception SQLException if a database access error occurs
	 */
	public int getMaxRows() throws SQLException
	{
		return maxrows;
	}

	/*
	 * Set the maximum number of rows
	 *
	 * @param max the new max rows limit; zero means unlimited
	 * @exception SQLException if a database access error occurs
	 * @see getMaxRows
	 */
	public void setMaxRows(int max) throws SQLException
	{
		maxrows = max;
	}

	/*
	 * If escape scanning is on (the default), the driver will do escape
	 * substitution before sending the SQL to the database.
	 *
	 * @param enable true to enable; false to disable
	 * @exception SQLException if a database access error occurs
	 */
	public void setEscapeProcessing(boolean enable) throws SQLException
	{
		escapeProcessing = enable;
	}

	/*
	 * The queryTimeout limit is the number of seconds the driver
	 * will wait for a Statement to execute.  If the limit is
	 * exceeded, a SQLException is thrown.
	 *
	 * @return the current query timeout limit in seconds; 0 = unlimited
	 * @exception SQLException if a database access error occurs
	 */
	public int getQueryTimeout() throws SQLException
	{
		return timeout;
	}

	/*
	 * Sets the queryTimeout limit
	 *
	 * @param seconds - the new query timeout limit in seconds
	 * @exception SQLException if a database access error occurs
	 */
	public void setQueryTimeout(int seconds) throws SQLException
	{
		timeout = seconds;
	}

	/*
	 * The first warning reported by calls on this Statement is
	 * returned.  A Statement's execute methods clear its SQLWarning
	 * chain.  Subsequent Statement warnings will be chained to this
	 * SQLWarning.
	 *
	 * <p>The Warning chain is automatically cleared each time a statement
	 * is (re)executed.
	 *
	 * <p><B>Note:</B>	If you are processing a ResultSet then any warnings
	 * associated with ResultSet reads will be chained on the ResultSet
	 * object.
	 *
	 * @return the first SQLWarning on null
	 * @exception SQLException if a database access error occurs
	 */
	public SQLWarning getWarnings() throws SQLException
	{
		return warnings;
	}

	/*
	 * The maxFieldSize limit (in bytes) is the maximum amount of
	 * data returned for any column value; it only applies to
	 * BINARY, VARBINARY, LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR
	 * columns.  If the limit is exceeded, the excess data is silently
	 * discarded.
	 *
	 * @return the current max column size limit; zero means unlimited
	 * @exception SQLException if a database access error occurs
	 */
	public int getMaxFieldSize() throws SQLException
	{
		return 8192;		// We cannot change this
	}

	/*
	 * Sets the maxFieldSize - NOT! - We throw an SQLException just
	 * to inform them to stop doing this.
	 *
	 * @param max the new max column size limit; zero means unlimited
	 * @exception SQLException if a database access error occurs
	 */
	public void setMaxFieldSize(int max) throws SQLException
	{
		throw new PSQLException("postgresql.stat.maxfieldsize");
	}

	/*
	 * After this call, getWarnings returns null until a new warning
	 * is reported for this Statement.
	 *
	 * @exception SQLException if a database access error occurs
	 */
	public void clearWarnings() throws SQLException
	{
		warnings = null;
	}

	/*
	 * Cancel can be used by one thread to cancel a statement that
	 * is being executed by another thread.
	 * <p>
	 * Not implemented, this method is a no-op.
	 *
	 * @exception SQLException only because thats the spec.
	 */
	public void cancel() throws SQLException
	{
		// FIXME: Cancel feature has been available since 6.4. Implement it here!
	}

	/*
	 * New in 7.1: Returns the Last inserted oid. This should be used, rather
	 * than the old method using getResultSet, which for executeUpdate returns
	 * null.
	 * @return OID of last insert
	 */
	public int getInsertedOID() throws SQLException
	{
		if (result == null)
			return 0;
		return ((org.postgresql.ResultSet) result).getInsertedOID();
	}

	/*
	 * getResultSet returns the current result as a ResultSet.	It
	 * should only be called once per result.
	 *
	 * @return the current result set; null if there are no more
	 * @exception SQLException if a database access error occurs (why?)
	 */
	public java.sql.ResultSet getResultSet() throws SQLException
	{
		if (result != null && ((org.postgresql.ResultSet) result).reallyResultSet())
			return result;
		return null;
	}

	/*
	 * In many cases, it is desirable to immediately release a
	 * Statement's database and JDBC resources instead of waiting
	 * for this to happen when it is automatically closed.	The
	 * close method provides this immediate release.
	 *
	 * <p><B>Note:</B> A Statement is automatically closed when it is
	 * garbage collected.  When a Statement is closed, its current
	 * ResultSet, if one exists, is also closed.
	 *
	 * @exception SQLException if a database access error occurs (why?)
	 */
	public void close() throws SQLException
	{
		// Force the ResultSet to close
		java.sql.ResultSet rs = getResultSet();
		if (rs != null)
			rs.close();

		// Disasociate it from us (For Garbage Collection)
		result = null;
	}

	/*
	 * Filter the SQL string of Java SQL Escape clauses.
	 *
	 * Currently implemented Escape clauses are those mentioned in 11.3
	 * in the specification. Basically we look through the sql string for
	 * {d xxx}, {t xxx} or {ts xxx} in non-string sql code. When we find
	 * them, we just strip the escape part leaving only the xxx part.
	 * So, something like "select * from x where d={d '2001-10-09'}" would
	 * return "select * from x where d= '2001-10-09'".
	 */
	protected static String escapeSQL(String sql)
	{
		// Since escape codes can only appear in SQL CODE, we keep track
		// of if we enter a string or not.
		StringBuffer newsql = new StringBuffer();
		short state = IN_SQLCODE;

		int i = -1;
		int len = sql.length();
		while (++i < len)
		{
			char c = sql.charAt(i);
			switch (state)
			{
				case IN_SQLCODE:
					if (c == '\'')				  // start of a string?
						state = IN_STRING;
					else if (c == '{')			  // start of an escape code?
						if (i + 1 < len)
						{
							char next = sql.charAt(i + 1);
							if (next == 'd')
							{
								state = ESC_TIMEDATE;
								i++;
								break;
							}
							else if (next == 't')
							{
								state = ESC_TIMEDATE;
								i += (i + 2 < len && sql.charAt(i + 2) == 's') ? 2 : 1;
								break;
							}
						}
					newsql.append(c);
					break;

				case IN_STRING:
					if (c == '\'')				   // end of string?
						state = IN_SQLCODE;
					else if (c == '\\')			   // a backslash?
						state = BACKSLASH;

					newsql.append(c);
					break;

				case BACKSLASH:
					state = IN_STRING;

					newsql.append(c);
					break;

				case ESC_TIMEDATE:
					if (c == '}')
						state = IN_SQLCODE;		  // end of escape code.
					else
						newsql.append(c);
					break;
			} // end switch
		}

		return newsql.toString();
	}
}
