/*
* This file or a portion of this file is licensed under the terms of
* the Globus Toolkit Public License, found in file GTPL, or at
* http://www.globus.org/toolkit/download/license.html. This notice must
* appear in redistributions of this file, with or without modification.
*
* Redistributions of this Software, with or without modification, must
* reproduce the GTPL in: (1) the Software, or (2) the Documentation or
* some other similar material which is provided with the Software (if
* any).
*
* Copyright 1999-2004 University of Chicago and The University of
* Southern California. All rights reserved.
*/
package org.griphyn.vdl.dbdriver;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;
import java.io.PrintWriter;
import java.io.IOException;
import org.griphyn.vdl.util.ChimeraProperties;
import edu.isi.pegasus.common.util.DynamicLoader;
import org.griphyn.vdl.util.Logging;
/**
* This common database interface that defines basic functionalities
* for interacting with backend SQL database. The implementation
* usually requires specific attention to the details between different
* databases, as each does things slightly different. The API provides
* a functionality that is independent of the schemas to be used.<p>
* The schema classes implement all their database access in terms of
* this database driver classes.<p>
* The separation of database driver and schema lowers the implementation
* cost, as only N driver and M schemas need to be implemented, instead
* of N x M schema-specific database-specific drivers.
*
* @author Jens-S. Vöckler
* @author Yong Zhao
* @version $Revision$
* @see org.griphyn.vdl.dbschema
*/
public abstract class DatabaseDriver
{
/**
* This variable keeps the JDBC handle for the database connection.
*/
protected Connection m_connection = null;
/**
* This list stores the prepared statements by their position.
* The constructor will initialize it. Explicit destruction can
* be requested per statement.
*/
protected Map m_prepared;
//
// class methods
//
/**
* Instantiates the appropriate child according to property values.
* This method is a factory. Currently, drivers may be instantiated
* multiple times.<p>
*
* @param dbDriverName is the name of the class that conforms to
* the DatabaseDriver API. This class will be dynamically loaded.
* The passed value should not be <code>null</code>.
* @param propertyPrefix is the property prefix string to use.
* @param arguments are arguments to the constructor of the driver
* to load. Please use "new Object[0]" for the argumentless default
* constructor.
*
* @exception ClassNotFoundException if the driver for the database
* cannot be loaded. You might want to check your CLASSPATH, too.
* @exception NoSuchMethodException if the driver's constructor interface
* does not comply with the database driver API.
* @exception InstantiationException if the driver class is an abstract
* class instead of a concrete implementation.
* @exception IllegalAccessException if the constructor for the driver
* class it not publicly accessible to this package.
* @exception InvocationTargetException if the constructor of the driver
* throws an exception while being dynamically loaded.
* @exception SQLException if the driver for the database can be
* loaded, but faults when initially accessing the database
*
* @see org.griphyn.vdl.util.ChimeraProperties#getDatabaseDriverName
*/
static public DatabaseDriver
loadDriver( String dbDriverName,
String propertyPrefix,
Object[] arguments )
throws ClassNotFoundException, IOException,
NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException,
SQLException
{
Logging log = Logging.instance();
log.log( "dbdriver", 3, "accessing loadDriver( " +
( dbDriverName == null ? "(null)" : dbDriverName ) + ", " +
( propertyPrefix == null ? "(null)" : propertyPrefix ) + " )" );
// determine the database driver to load
if ( dbDriverName == null ) {
dbDriverName = ChimeraProperties.instance()
.getDatabaseDriverName( propertyPrefix );
if ( dbDriverName == null )
throw new RuntimeException( "You need to specify the " +
propertyPrefix + " property" );
}
// syntactic sugar adds absolute class prefix
if ( dbDriverName.indexOf('.') == -1 ) {
// how about xxx.getClass().getPackage().getName()?
dbDriverName = "org.griphyn.vdl.dbdriver." + dbDriverName;
}
// POSTCONDITION: we have now a fully-qualified class name
log.log( "dbdriver", 3, "trying to load " + dbDriverName );
DynamicLoader dl = new DynamicLoader(dbDriverName);
DatabaseDriver result = (DatabaseDriver) dl.instantiate(arguments);
// done
if ( result == null )
log.log( "dbdriver", 0, "unable to load " + dbDriverName );
else
log.log( "dbdriver", 3, "successfully loaded " + dbDriverName );
return result;
}
/**
* Convenience method instantiates the appropriate child according to
* property values. Effectively, the following abbreviation is called:
*
* <pre>
* loadDriver( null, propertyPrefix, new Object[0] );
* </pre>
*
* @param propertyPrefix is the property prefix string to use.
*
* @exception ClassNotFoundException if the driver for the database
* cannot be loaded. You might want to check your CLASSPATH, too.
* @exception NoSuchMethodException if the driver's constructor interface
* does not comply with the database driver API.
* @exception InstantiationException if the driver class is an abstract
* class instead of a concrete implementation.
* @exception IllegalAccessException if the constructor for the driver
* class it not publicly accessible to this package.
* @exception InvocationTargetException if the constructor of the driver
* throws an exception while being dynamically loaded.
* @exception SQLException if the driver for the database can be
* loaded, but faults when initially accessing the database
*
* @see #loadDriver( String, String, Object[] )
*/
static public DatabaseDriver
loadDriver( String propertyPrefix )
throws ClassNotFoundException, IOException,
NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException,
SQLException
{
return loadDriver( null, propertyPrefix, new Object[0] );
}
/**
* Default constructor. As the constructor will do nothing, please use
* the connect method to obtain a database connection. This is the
* constructor that will be invoked when dynamically loading a driver.
*
* @see #connect( String, Properties, Set )
*/
public DatabaseDriver()
{
this.m_connection = null;
this.m_prepared = new TreeMap();
}
/**
* Establishes a connection to the specified database. The parameters
* will often be ignored or abused for different purposes on different
* backends. It is assumed that the connection is not in auto-commit
* mode, and explicit commits must be issued.<p>
* Essentially, the deriving class will overwrite their connect method
* to fill in the appropriate driver, and otherwise just call this
* method.
*
* @param driver is the Java class name of the database driver package
* @param url the contact string to database, or schema location
* @param info additional parameters, usually username and password
* @param tables is a set of all table names in the schema. The
* existence of all tables will be checked to verify
* that the schema is active in the database.
* @return true if the connection succeeded, false otherwise. Usually,
* false is returned, if the any of the tables or sequences is missing.
*
* @see #connect( String, Properties, Set )
* @see org.griphyn.vdl.util.ChimeraProperties#getDatabaseDriverName
* @see org.griphyn.vdl.util.ChimeraProperties#getDatabaseURL
* @see org.griphyn.vdl.util.ChimeraProperties#getDatabaseDriverProperties
* @exception if the driver is incapable of establishing a connection.
*/
protected boolean connect( String driver,
String url,
Properties info,
Set tables )
throws SQLException, ClassNotFoundException
{
// load specificed driver class into memory
Class.forName(driver);
Logging.instance().log("xaction", 1, "START connect to dbase" );
m_connection = DriverManager.getConnection( url, info );
DriverManager.setLogWriter( new PrintWriter(System.err) );
Logging.instance().log("xaction", 1, "FINAL connected to dbase" );
// determine that database version and driver version match
this.driverMatch();
// disable auto commit, required for transaction (and speed).
Logging.instance().log("xaction", 1, "START disable auto-commit" );
m_connection.setAutoCommit( false );
Logging.instance().log("xaction", 1, "FINAL disabled auto-commit" );
// auto-disconnect, should we forget it, or die in an orderly fashion
Runtime.getRuntime().addShutdownHook( new Thread() {
public void run() {
try {
disconnect();
} catch (SQLException e) {
e.printStackTrace();
}
}
});
// final function return value
boolean result = true;
/* ### disabled
// check for the presence of all tables in schema
if ( result && tables != null && tables.size() > 0 ) {
Logging.instance().log("xaction", 1, "START checking for tables" );
List columns = new ArrayList();
columns.add( "tablename" );
Map where = new HashMap();
where.put( "tableowner", info.get("username") );
Set temp = new TreeSet();
ResultSet rs = this.select( columns, "pg_tables", where, null );
while ( rs.next() ) {
temp.add( rs.getString() );
}
result = temp.containsAll( tables );
Logging.instance().log("xaction", 1, "FINAL checking for tables" );
}
### */
return result;
}
/**
* Establish a connection to your database. The parameters will often
* be ignored or abused for different purposes on different backends.
* It is assumed that the connection is not in auto-commit mode, and
* explicit commits must be issued.
*
* @param url the contact string to database, or schema location
* @param info additional parameters, usually username and password
* @param tables is a set of all table names in the schema. The
* existence of all tables will be checked to verify
* that the schema is active in the database.
* @return true if the connection succeeded, false otherwise. Usually,
* false is returned, if the any of the tables or sequences is missing.
* @exception if the driver is incapable of establishing a connection.
*/
abstract public boolean connect( String url,
Properties info,
Set tables )
throws SQLException, ClassNotFoundException;
/**
* Determines, if the JDBC driver is the right one for the database we
* talk to. Throws an exception if not.
*/
public void driverMatch()
throws SQLException
{
// empty on purpose
}
/**
* Close an established database connection.
*
* @exception if the driver threw up on the data.
*/
public void disconnect()
throws SQLException
{
if ( this.m_connection != null ) {
this.m_connection.close();
this.m_connection = null;
}
}
/**
* Closes an open connection to the database whenever this object
* is destroyed. This is still not foolproof.
*
*/
protected void finalize()
throws Throwable
{
if ( this.m_connection != null ) {
this.m_connection.close();
this.m_connection = null;
}
super.finalize();
}
/**
* Determines, if the backend is expensive, and results should be cached.
* Ideally, this will move transparently into the backend itself.
* @return true if caching is advisable, false for no caching.
*/
abstract public boolean cachingMakesSense();
/**
* Quotes a string that may contain special SQL characters.
* @param s is the raw string.
* @return the quoted string, which may be just the input string.
*/
public String quote( String s )
{
// not implemented.
return s;
}
/**
* Commits the latest changes to the database.
* @exception SQLException is propagated from the commit.
*/
public void commit()
throws SQLException
{
this.m_connection.commit();
}
/**
* Rolls back the latest changes to the database. Some databases may
* be incapable of rolling back.
* @exception SQLException is propagated from the rollback operation.
*/
public void rollback()
throws SQLException
{
this.m_connection.rollback();
}
/**
* Clears all warnings reported for this database driver. After a call
* to this method, the internal warnings are cleared until the next one
* occurs.
*/
public void clearWarnings()
throws SQLException
{
this.m_connection.clearWarnings();
}
/**
* Retrieves the first warning reported by calls on this Connection
* object.
* @return the first SQLWarning object or null if there are none
* @throws SQLException if a database access error occurs or this
* method is called on a closed connection
* @see java.sql.Connection#getWarnings
*/
public SQLWarning getWarnings()
throws SQLException
{
return this.m_connection.getWarnings();
}
/**
* Obtains the next value from a sequence. JDBC drivers which allow
* explicit access to sequence generator will return a valid value
* in this function. All other JDBC drivers should return -1.
*
* @param name is the name of the sequence.
* @return the next sequence number.
* @exception if something goes wrong while fetching the new value.
*/
abstract public long sequence1( String name )
throws SQLException;
/**
* Obtains the sequence value for the current statement. JDBC driver
* that permit insertion of NULL into auto-increment value should use
* this method to return the inserted ID value via the statements
* getGeneratedKeys(). Other JDBC drivers should treat return the
* parametric id.
*
* @param s is a statment or prepared statement
* @param name is the name of the sequence.
* @param pos is the column number of the auto-increment column.
* @return the next sequence number.
* @exception if something goes wrong while fetching the new value.
*/
abstract public long sequence2( Statement s, String name, int pos )
throws SQLException;
/**
* Removes all rows that match the provided keyset from a table.
* @param table is the name of the table to remove values from
* @param columns is a set of column names and their associated
* values to select the removed columns. The map
* may be null to remove all rows in a table.
* @return the number of rows removed.
* @exception if something goes wrong while removing the values.
*/
public int delete( String table,
Map columns )
throws SQLException
{
StringBuffer request = new StringBuffer();
request.append( "DELETE FROM " ).append(table);
if ( columns != null && columns.size() > 0 ) {
request.append(" WHERE ");
for ( Iterator i=columns.entrySet().iterator(); i.hasNext(); ) {
Map.Entry me = (Map.Entry) i.next();
request.append( (String) me.getKey() ).append("='");
request.append( this.quote((String) me.getValue()) ).append('\'');
if ( i.hasNext() ) request.append( " AND " );
}
}
String query = request.toString();
Logging.instance().log( "sql", 2, query );
Logging.instance().log( "xaction", 1, "START DELETE in " + table );
int count = m_connection.createStatement().executeUpdate(query);
Logging.instance().log( "xaction", 1, "FINAL DELETE in " + table +
": " + count );
return count;
}
/**
* Inserts a row in one given database table.
*
* @param table is the name of the table to insert into.
* @param keycolumns is a set of primary keys and their associated values.
* For special tables, the primary key set may be null
* or empty (e.g. a table without primary keys).
* @param columns is a set of regular keys and their associated values.
* @return the number of rows affected.
* @exception if something goes wrong while inserting the values.
*/
public long insert( String table,
Map keycolumns,
Map columns )
throws SQLException
{
StringBuffer request = new StringBuffer();
StringBuffer values = new StringBuffer();
request.append( "INSERT INTO " ).append(table).append('(');
values.append('(');
// conditionally add primary key columns, if they exist
if ( keycolumns != null && keycolumns.size() > 0 ) {
for ( Iterator i=keycolumns.entrySet().iterator(); i.hasNext(); ) {
Map.Entry me = (Map.Entry) i.next();
request.append( (String) me.getKey() );
values.append( quote( (String) me.getValue()) );
if ( i.hasNext() ) {
request.append( ", " );
values.append( ", " );
}
}
}
// conditionally add all columns
if ( columns != null && columns.size() > 0 ) {
for ( Iterator i=columns.entrySet().iterator(); i.hasNext(); ) {
Map.Entry me = (Map.Entry) i.next();
request.append( (String) me.getKey() );
values.append( quote((String) me.getValue()) );
if ( i.hasNext() ) {
request.append( ", " );
values.append( ", " );
}
}
}
String query = request.toString() + " VALUES " + values.toString();
Logging.instance().log( "sql", 2, query );
Logging.instance().log( "xaction", 1, "START INSERT in " + table );
Statement st = m_connection.createStatement();
long count = st.executeUpdate(query);
Logging.instance().log( "xaction", 1, "FINAL INSERT in " + table +
": " + count );
return count;
}
/**
* Updates matching rows in one given database table.
* @param table is the name of the table to insert into.
* @param keycolumns is a set of primary keys and their associated values.
* For special tables, the primary key set may be null
* or empty (e.g. a table without primary keys).
* @param columns is a set of regular keys and their associated values.
* @return the number of rows affected
* @exception if something goes wrong while updating the values.
*/
public int update( String table,
Map keycolumns,
Map columns )
throws SQLException
{
StringBuffer request = new StringBuffer();
request.append( "UPDATE " ).append(table).append(' ');
// unconditionally add all columns
for ( Iterator i=columns.entrySet().iterator(); i.hasNext(); ) {
Map.Entry me = (Map.Entry) i.next();
request.append( "SET ").append( (String) me.getKey() ).append("='");
request.append( quote((String) me.getValue()) ).append('\'');
if ( i.hasNext() ) request.append( ", " );
}
// conditionally add primary key columns, if they exist
if ( keycolumns != null && keycolumns.size() > 0 ) {
request.append( " WHERE " );
for ( Iterator i=keycolumns.entrySet().iterator(); i.hasNext(); ) {
Map.Entry me = (Map.Entry) i.next();
request.append( (String) me.getKey() ).append("='");
request.append( quote((String) me.getValue()) ).append('\'');
if ( i.hasNext() ) request.append( " AND " );
}
}
// so far, so good
String query = request.toString();
Logging.instance().log( "sql", 2, query );
Logging.instance().log( "xaction", 1, "START UPDATE in " + table );
int count = m_connection.createStatement().executeUpdate(query);
Logging.instance().log( "xaction", 1, "FINAL UPDATE in " + table +
": " + count );
return count;
}
/**
* Selects any rows in one or more colums from one or more tables
* restricted by some condition, possibly ordered.
* @param select is the ordered set of column names to select, or
* simply a one-value list with an asterisk.
* @param table is the name of the table to select from.
* @param where is a collection of column names and values they must equal.
* @param order is an optional ordering string.
*/
public ResultSet select( List select,
String table,
Map where,
String order )
throws SQLException
{
StringBuffer request = new StringBuffer();
request.append( "SELECT " );
for ( Iterator i=select.iterator(); i.hasNext(); ) {
request.append( (String) i.next() );
if ( i.hasNext() ) request.append(',');
}
request.append( " FROM " ).append(table);
if ( where != null && where.size() > 0 ) {
request.append( " WHERE " );
for ( Iterator i=where.entrySet().iterator(); i.hasNext(); ) {
Map.Entry me = (Map.Entry) i.next();
request.append( (String) me.getKey() ).append("=\'");
request.append( quote((String) me.getValue()) ).append('\'');
if ( i.hasNext() ) request.append(" AND ");
}
}
if ( order != null && order.length() > 0 ) request.append(order);
String query = request.toString();
Logging.instance().log( "sql", 2, query );
Logging.instance().log( "xaction", 1, "START SELECT FROM " + table );
ResultSet result = m_connection.createStatement().executeQuery(query);
Logging.instance().log( "xaction", 1, "FINAL SELECT FROM " + table );
return result;
}
/**
* Selects any rows in one or more colums from one or more tables
* restricted by some condition that allows operators. Permissable
* operators include =, <>, >, >=, <, <=, like, etc.
* possibly ordered.
*
* @param select is the ordered set of column names to select, or
* simply a one-value list with an asterisk.
* @param table is the name of the table to select from.
* @param where is a collection of column names and values
* @param operator is a collection of column names and operators
* if no entry is found for the name, then use '=' as default
* @param order is an optional ordering string.
*/
public ResultSet select( List select,
String table,
Map where,
Map operator,
String order )
throws SQLException
{
StringBuffer request = new StringBuffer();
Logging l = Logging.instance();
request.append( "SELECT " );
for ( Iterator i=select.iterator(); i.hasNext(); ) {
request.append( (String) i.next() );
if ( i.hasNext() ) request.append(',');
}
request.append( " FROM " ).append(table);
if ( where != null && where.size() > 0 ) {
l.log( "xaction", 1, "adding WHERE clause" );
request.append( " WHERE " );
for ( Iterator i=where.keySet().iterator(); i.hasNext(); ) {
String key = (String) i.next();
request.append( key );
String op = (String) operator.get( key );
request.append( op == null ? "=\'" : ( " " + op + " \'") );
request.append( quote((String) where.get(key)) ).append('\'');
if ( i.hasNext() ) request.append(" AND ");
}
} else {
l.log( "xaction", 1, "no WHERE clause" );
}
if ( order != null && order.length() > 0 ) request.append(order);
String query = request.toString();
l.log( "sql", 2, query );
l.log( "xaction", 1, "START " + query );
ResultSet result = m_connection.createStatement().executeQuery(query);
l.log( "xaction", 1, "FINAL " + query );
return result;
}
/**
* Drills a hole into the nice database driver abstraction to the JDBC3
* level. Use with caution.
* @param query is an SQL query statement.
* @exception SQLException if something goes wrong during the query
*/
public ResultSet backdoor( String query )
throws SQLException
{
Logging.instance().log( "sql", 2, query );
Logging.instance().log( "xaction", 1, "START " + query );
ResultSet result = m_connection.createStatement().executeQuery(query);
Logging.instance().log( "xaction", 1, "FINAL " + query );
return result;
}
//
// handle prepared statements
//
/**
* Predicate to tell the schema, if using a string instead of number
* will result in the speedier index scans instead of sequential scans.
* PostGreSQL has this problem, but using strings in the place of
* integers may not be universally portable.
*
* @return true, if using strings instead of integers and bigints
* will yield better performance.
*
*/
abstract public boolean preferString();
/**
* Inserts a new prepared statement into the list of prepared
* statements. If the id is already taken, it will be
* rejected.
*
* @param id is the id into which to parse the statement
* @param statement is the before-parsing statement string
* @return true, if the statement was parsed and added at the id,
* false, if the id was already taken. The statement is not parsed
* in that case.
*
* @exception SQLException may be thrown by parsing the statement.
*
* @see #removePreparedStatement( String )
* @see #getPreparedStatement( String )
* @see #cancelPreparedStatement( String )
*/
public boolean addPreparedStatement( String id,
String statement )
throws SQLException
{
// sanity check
if ( this.m_prepared == null )
throw new RuntimeException( "You forgot to initialize the prepared statement length" );
// duplicate check
if ( this.m_prepared.containsKey(id) )
return false;
// add to id
Logging.instance().log( "sql", 2, statement );
Logging.instance().log( "xaction", 1, "START prepare " + statement );
try {
this.m_prepared.put( id, this.m_connection.prepareStatement(statement) );
} catch ( SQLException original ) {
SQLException sql = original;
for ( int i=0; sql != null; ++i ) {
Logging.instance().log( "sql", 0, "SQL error " + i + ": " +
sql.getErrorCode() + ": " + sql.getMessage() );
sql = sql.getNextException();
}
throw original;
} catch ( NullPointerException e ) {
Logging.instance().log( "sql", 0, "stumbled over null" );
e.printStackTrace();
System.exit(1);
}
Logging.instance().log( "xaction", 1, "FINAL prepare " + statement );
return true;
}
/**
* Inserts a new prepared statement into the list of prepared
* statements. If the id is already taken, it will be
* rejected. This method can only be used with JDBC drivers
* that support auto-increment columns. It might fail with JDBC
* drivers that do not support auto-increment columns, depending
* on the driver's implementation.
*
* @param id is the id into which to parse the statement
* @param statement is the before-parsing statement string
* @param autoGeneratedKeys is true, if the statement should reserve
* space to return autoinc columns, false, if the statement does not
* have any such keys.
* @return true, if the statement was parsed and added at the id,
* false, if the id was already taken. The statement is not parsed
* in that case.
*
* @exception SQLException may be thrown by parsing the statement.
*
* @see #removePreparedStatement( String )
* @see #getPreparedStatement( String )
* @see #cancelPreparedStatement( String )
*/
protected boolean addPreparedStatement( String id,
String statement,
boolean autoGeneratedKeys )
throws SQLException
{
// sanity check
if ( this.m_prepared == null )
throw new RuntimeException( "You forgot to initialize the prepared statement length" );
// duplicate check
if ( this.m_prepared.containsKey(id) )
return false;
// add to id
Logging.instance().log( "sql", 2, statement );
Logging.instance().log( "xaction", 1, "START prepare " + statement );
try {
this.m_prepared.put( id,
this.m_connection.prepareStatement( statement,
autoGeneratedKeys ?
Statement.RETURN_GENERATED_KEYS :
Statement.NO_GENERATED_KEYS ) );
} catch ( SQLException original ) {
SQLException sql = original;
for ( int i=0; sql != null; ++i ) {
Logging.instance().log( "sql", 0, "SQL error " + i + ": " +
sql.getErrorCode() + ": " + sql.getMessage() );
sql = sql.getNextException();
}
throw original;
} catch ( NullPointerException e ) {
Logging.instance().log( "sql", 0, "stumbled over null" );
e.printStackTrace();
System.exit(1);
}
Logging.instance().log( "xaction", 1, "FINAL prepare " + statement );
return true;
}
/**
* Inserts a new prepared statement into the list of prepared
* statements. If the id is already taken, an error will be
* printed and execution aborted.
*
* @param id is the id into which to parse the statement
* @param statement is the before-parsing statement string
* @return true, if the statement was parsed and added at the id,
* false, if the id was already taken. The statement is not parsed
* in that case.
*
* @exception SQLException may be thrown by parsing the statement.
*
* @see #addPreparedStatement( String, String )
* @see #getPreparedStatement( String )
* @see #cancelPreparedStatement( String )
*/
public boolean insertPreparedStatement( String id,
String statement )
throws SQLException
{
boolean result = addPreparedStatement( id, statement );
if ( result == false ) {
System.err.println( "Duplicate key " + id );
System.exit(1);
}
return result;
}
/**
* Obtains a reference to a prepared statement to be used from
* the caller. This function will also reset the input values
* in the prepared statement.
*
* @param id is the place of the statement to free up.
* @exception SQLException if the database does not like the disconnect.
*
* @see java.sql.PreparedStatement#clearParameters()
* @see #addPreparedStatement( String, String )
* @see #removePreparedStatement( String )
* @see #cancelPreparedStatement( String )
*/
public PreparedStatement getPreparedStatement( String id )
throws SQLException
{
PreparedStatement result = (PreparedStatement) this.m_prepared.get(id);
if ( result != null ) result.clearParameters();
else throw new SQLException( "unknown prepared statement " + id );
return result;
}
/**
* Explicitely requests a prepared id to be destroyed and its resources
* freed. Multiple invocation for the same id are harmless.
*
* @param id is the place of the statement to free up.
* @exception SQLException if the database does not like the disconnect.
*
* @see #addPreparedStatement( String, String )
* @see #getPreparedStatement( String )
* @see #cancelPreparedStatement( String )
*/
public void removePreparedStatement( String id )
throws SQLException
{
PreparedStatement ps = (PreparedStatement) this.m_prepared.remove(id);
if ( ps != null ) ps.close();
}
/**
* Cancels and resets all previous values of a prepared statement.
*
* @param id is the id for which to obtain the previously
* prepared statement.
*
* @exception SQLException if a database access error occurs while
* clearing the parameters.
* @exception ArrayIndexOutOfBoundsException if a non-existing id
* is being requested.
*
* @see java.sql.PreparedStatement#clearParameters()
* @see #addPreparedStatement( String, String )
* @see #getPreparedStatement( String )
* @see #removePreparedStatement( String )
*/
public void cancelPreparedStatement( String id )
throws SQLException, ArrayIndexOutOfBoundsException
{
PreparedStatement ps = (PreparedStatement) this.m_prepared.get(id);
if ( ps != null ) {
ps.cancel();
ps.clearParameters();
}
}
}