/*
* 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 org.griphyn.vdl.dbdriver.DatabaseDriver;
import java.sql.*;
import java.util.*;
import org.griphyn.vdl.util.*;
/**
* This class implements the driver API for the PostGreSQL 7.3.* and
* 7.4.* series database. Please note that at this point, we cannot
* recommend to use Postgres 8.0.*.
*
* @author Jens-S. Vöckler
* @author Yong Zhao
* @version $Revision$
*
* @see DatabaseDriver
* @see org.griphyn.vdl.dbschema
*/
public class Postgres extends DatabaseDriver
{
/**
* Caches the driver version major, because this has side-effects on
* the behavior.
*/
private int m_driver_major = -1;
/**
* Default constructor. As the constructor will do nothing, please use
* the connect method to obtain a database connection.
*
* @see #connect( String, Properties, Set )
*/
public Postgres()
{
super();
}
/**
* 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.
*/
public boolean connect( String url,
Properties info,
Set tables )
throws SQLException, ClassNotFoundException
{
// load PostGreSQL driver class into memory
boolean save = this.connect( "org.postgresql.Driver", url, info, tables );
// add preparsed statement for sequence
this.addPreparedStatement( "vds.sequence", "SELECT nextval(?)" );
// done
return save;
}
/**
* 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.
*/
public boolean cachingMakesSense()
{
return true;
}
/**
* 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
{
DatabaseMetaData meta = m_connection.getMetaData();
int driver_major = meta.getDriverMajorVersion();
int driver_minor = meta.getDriverMinorVersion();
String database = meta.getDatabaseProductVersion();
boolean flag = false;
int database_major = -1;
int database_minor = -1;
try {
database_major = meta.getDatabaseMajorVersion();
database_minor = meta.getDatabaseMinorVersion();
} catch ( SQLException e ) {
// check for "This method is not yet implemented"
if ( e.getErrorCode() == 0 ) flag = true;
else throw e;
}
if ( flag ) {
// use old-style check
String jdbc = driver_major + "." + driver_minor;
if ( ! database.startsWith(jdbc) )
throw new RuntimeException(
"JDBC driver " + jdbc +
" does not match database version " + database );
} else {
// use new-style check - requires 7.4 JDBC driver
if ( driver_major < database_major ||
( driver_major == database_major && driver_minor < database_minor ) )
throw new RuntimeException(
"JDBC driver " +
driver_major + "." + driver_minor +
" does not match database version " +
database_major + "." + database_minor );
}
}
/**
* 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 )
{
if ( s.indexOf('\'') != -1 ) {
StringBuffer result = new StringBuffer();
for ( int i=0; i < s.length(); ++i ) {
char ch = s.charAt(i);
result.append(ch);
if ( ch == '\'' ) result.append(ch);
}
return result.toString();
} else {
return s;
}
}
/**
* Obtains the next value from a sequence. Postgres uses explicit
* sequence generators, so this function will return the new id.
*
* @param name is the name of the sequence.
* @return the next sequence number.
* @exception if something goes wrong while fetching the new value.
*/
public long sequence1( String name )
throws SQLException
{
PreparedStatement ps = this.getPreparedStatement("vds.sequence");
Logging.instance().log( "sql", 2, "SELECT nextval(" + name + ")" );
Logging.instance().log( "xaction", 1, "START sequence " + name );
// obtain new sequence number
ps.setString( 1, name );
ResultSet rs = ps.executeQuery();
rs.next();
long result = rs.getLong("nextval");
rs.close();
Logging.instance().log("xaction", 1, "FINAL sequence " + name +
" = " + result );
// done
return result;
}
/**
* Obtains the sequence value for the current statement. Postgres does
* not permit NULL-driven auto-increment columns. Postgres uses
* explicit sequence generators, so this function always returns -1.
*
* @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.
*/
public long sequence2( Statement s, String name, int pos )
throws SQLException
{
// should not be called here
return -1;
}
/**
* Predicate to tell the schema, if using a string instead of number
* will result in the speedier index scans instead of sequential scans.
* PostGreSQL suffers from this problem.
*
* @return true, if using strings instead of integers and bigints
* will yield better performance.
*
*/
public boolean preferString()
{
if ( m_driver_major == -1 ) {
try {
DatabaseMetaData meta = m_connection.getMetaData();
m_driver_major = meta.getDriverMajorVersion();
} catch ( SQLException e ) {
// cache failure
m_driver_major = 0;
}
}
return ( m_driver_major < 8 );
}
}