From 868ebd38b4ded1c2f11c03539b368ad9233a4256 Mon Sep 17 00:00:00 2001 From: mdb Date: Mon, 23 Jul 2007 21:32:22 +0000 Subject: [PATCH] Phase one of the database dialect supports for Depot from Zell. This is all enhancements to non-Depot JDBC utilities (and dialect agnosticizing TransitionRepository though we could accomplish that by Depot-izing it as well). git-svn-id: https://samskivert.googlecode.com/svn/trunk@2134 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- src/java/com/samskivert/jdbc/BaseLiaison.java | 233 ++++++++++++++++++ .../samskivert/jdbc/ConnectionProvider.java | 82 +++--- .../com/samskivert/jdbc/DatabaseLiaison.java | 148 ++++++++--- .../com/samskivert/jdbc/DefaultLiaison.java | 48 +++- src/java/com/samskivert/jdbc/JDBCUtil.java | 1 - .../com/samskivert/jdbc/LiaisonRegistry.java | 43 ++-- .../com/samskivert/jdbc/MySQLLiaison.java | 92 +++++-- .../samskivert/jdbc/PostgreSQLLiaison.java | 113 +++++++++ .../com/samskivert/jdbc/SimpleRepository.java | 165 ++++++------- .../jdbc/StaticConnectionProvider.java | 121 +++++---- .../samskivert/jdbc/TransitionRepository.java | 43 ++-- 11 files changed, 803 insertions(+), 286 deletions(-) create mode 100644 src/java/com/samskivert/jdbc/BaseLiaison.java create mode 100644 src/java/com/samskivert/jdbc/PostgreSQLLiaison.java diff --git a/src/java/com/samskivert/jdbc/BaseLiaison.java b/src/java/com/samskivert/jdbc/BaseLiaison.java new file mode 100644 index 00000000..5f94f0a4 --- /dev/null +++ b/src/java/com/samskivert/jdbc/BaseLiaison.java @@ -0,0 +1,233 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import com.samskivert.Log; +import com.samskivert.util.StringUtil; + +/** + * A superclass to help with the shrinking subset of SQL our supported dialects can agree on, + * or when there is disagreement, implement the most standard-compliant version and let the + * dialectal sub-classes override. + */ +public abstract class BaseLiaison implements DatabaseLiaison +{ + public BaseLiaison () + { + super(); + } + + // from DatabaseLiaison + public boolean tableExists (Connection conn, String name) throws SQLException + { + ResultSet rs = conn.getMetaData().getTables("", "", name, null); + while (rs.next()) { + String tname = rs.getString("TABLE_NAME"); + if (name.equals(tname)) { + return true; + } + } + return false; + } + + // from DatabaseLiaison + public boolean tableContainsColumn (Connection conn, String table, String column) + throws SQLException + { + ResultSet rs = conn.getMetaData().getColumns("", "", table, column); + while (rs.next()) { + String tname = rs.getString("TABLE_NAME"); + String cname = rs.getString("COLUMN_NAME"); + if (tname.equals(table) && cname.equals(column)) { + return true; + } + } + return false; + } + + // from DatabaseLiaison + public boolean tableContainsIndex (Connection conn, String table, String index) + throws SQLException + { + ResultSet rs = conn.getMetaData().getIndexInfo("", "", table, false, true); + while (rs.next()) { + String tname = rs.getString("TABLE_NAME"); + String iname = rs.getString("INDEX_NAME"); + if (tname.equals(table) && index.equals(iname)) { + return true; + } + } + return false; + } + + // from DatabaseLiaison + public boolean addIndexToTable (Connection conn, String table, String[] columns, String ixName) + throws SQLException + { + if (tableContainsIndex(conn, table, ixName)) { + return false; + } + ixName = (ixName != null ? ixName : StringUtil.join(columns, "_")); + + StringBuilder update = new StringBuilder("CREATE INDEX ").append(indexSQL(ixName)). + append(" on ").append(tableSQL(table)).append(" ("); + for (int ii = 0; ii < columns.length; ii ++) { + if (ii > 0) { + update.append(", "); + } + update.append(columnSQL(columns[ii])); + } + update.append(")"); + + PreparedStatement stmt = null; + try { + stmt = conn.prepareStatement(update.toString()); + stmt.executeUpdate(); + } finally { + JDBCUtil.close(stmt); + } + + Log.info("Database index '" + ixName + "' added to table '" + table + "'"); + return true; + } + + // from DatabaseLiaison + public boolean changeColumn (Connection conn, String table, String column, String definition) + throws SQLException + { + PreparedStatement stmt = null; + try { + stmt = conn.prepareStatement( + "ALTER TABLE " + tableSQL(table) + " CHANGE " + columnSQL(column) + + " " + column + " " + definition); + stmt.executeUpdate(); + } finally { + JDBCUtil.close(stmt); + } + + Log.info("Database column '" + column + "' of table '" + table + + "' modified to have definition '" + definition + "'."); + return true; + } + + // from DatabaseLiaison + public boolean renameColumn (Connection conn, String table, String from, String to) + throws SQLException + { + PreparedStatement stmt = null; + try { + stmt = conn.prepareStatement( + "ALTER TABLE " + tableSQL(table) + " RENAME COLUMN " + columnSQL(from) + + " TO " + columnSQL(to)); + if (stmt.executeUpdate() == 1) { + Log.info("Renamed column '" + from + "' on table '" + table + "' to '" + to + "'"); + } + } finally { + JDBCUtil.close(stmt); + } + return true; + } + + // from DatabaseLiaison + public boolean dropColumn (Connection conn, String table, String column) throws SQLException + { + if (!tableContainsColumn(conn, table, column)) { + return false; + } + + PreparedStatement stmt = null; + try { + stmt = conn.prepareStatement( + "ALTER TABLE " + tableSQL(table) + " DROP COLUMN " + columnSQL(column)); + if (stmt.executeUpdate() == 1) { + Log.info("Database index '" + column + "' removed from table '" + table + "'."); + } + } finally { + JDBCUtil.close(stmt); + } + return true; + } + + // from DatabaseLiaison + public boolean createTableIfMissing ( + Connection conn, String table, String[] columns, String[] definitions, + String[][] uniqueConstraintColumns, String[] primaryKeyColumns) + throws SQLException + { + if (tableExists(conn, table)) { + return false; + } + if (columns.length != definitions.length) { + throw new IllegalArgumentException("Column name and definition number mismatch"); + } + + StringBuilder builder = + new StringBuilder("CREATE TABLE ").append(tableSQL(table)).append(" ("); + for (int ii = 0; ii < columns.length; ii ++) { + if (ii > 0) { + builder.append(", "); + } + builder.append(columnSQL(columns[ii])).append(" ").append(definitions[ii]); + } + + if (uniqueConstraintColumns != null && uniqueConstraintColumns.length > 0) { + for (String[] uCols : uniqueConstraintColumns) { + builder.append(", UNIQUE ("); + for (int ii = 0; ii < uCols.length; ii ++) { + if (ii > 0) { + builder.append(", "); + } + builder.append(columnSQL(uCols[ii])); + } + builder.append(")"); + } + } + + if (primaryKeyColumns != null && primaryKeyColumns.length > 0) { + builder.append(", PRIMARY KEY ("); + for (int ii = 0; ii < primaryKeyColumns.length; ii ++) { + if (ii > 0) { + builder.append(", "); + } + builder.append(columnSQL(primaryKeyColumns[ii])); + } + builder.append(")"); + } + + builder.append(")"); + + Statement stmt = conn.createStatement(); + try { + stmt.executeUpdate(builder.toString()); + } finally { + JDBCUtil.close(stmt); + } + + Log.info("Database table '" + table + "' created."); + return true; + } +} diff --git a/src/java/com/samskivert/jdbc/ConnectionProvider.java b/src/java/com/samskivert/jdbc/ConnectionProvider.java index 8ff7e609..019ce2b5 100644 --- a/src/java/com/samskivert/jdbc/ConnectionProvider.java +++ b/src/java/com/samskivert/jdbc/ConnectionProvider.java @@ -25,69 +25,65 @@ import java.sql.*; import com.samskivert.io.PersistenceException; /** - * As the repository aims to interface with whatever database pooling - * services a project cares to use, it obtains all of its database - * connections through a connection provider. The connection provider - * provides connections based on a database identifier (a string) which - * identifies a particular connection to a particular database. A user of - * these services would then coordinate the database identifier (or - * identifiers) used to invoke a database operation with the database - * connections that are returned by the connection provider that they - * provided to the repository at construct time. + * As the repository aims to interface with whatever database pooling services a project cares to + * use, it obtains all of its database connections through a connection provider. The connection + * provider provides connections based on a database identifier (a string) which identifies a + * particular connection to a particular database. A user of these services would then coordinate + * the database identifier (or identifiers) used to invoke a database operation with the database + * connections that are returned by the connection provider that they provided to the repository at + * construct time. */ public interface ConnectionProvider { /** - * Obtains a database connection based on the supplied database - * identifier. The repository expects to have exclusive use of this - * connection instance until it releases it. This connection will be - * released subsequently with a call to {@link #releaseConnection} or - * {@link #connectionFailed} depending on the circumstances of the - * release. close() will not be called on the - * connection. It is up to the connection provider to close the - * connection when it is released if appropriate. + * Obtains a database connection based on the supplied database identifier. The repository + * expects to have exclusive use of this connection instance until it releases it. This + * connection will be released subsequently with a call to {@link #releaseConnection} or {@link + * #connectionFailed} depending on the circumstances of the release. close() + * will not be called on the connection. It is up to the connection provider to close + * the connection when it is released if appropriate. * * @param ident the database connection identifier. - * @param readOnly whether or not the connection may be to a read-only - * mirror of the repository. + * @param readOnly whether or not the connection may be to a read-only mirror of the + * repository. * - * @return an active JDBC connection (which may have come from a - * connection pool). + * @return an active JDBC connection (which may have come from a connection pool). * - * @exception PersistenceException thrown if a problem occurs trying - * to open the requested connection. + * @exception PersistenceException thrown if a problem occurs trying to open the requested + * connection. */ public Connection getConnection (String ident, boolean readOnly) throws PersistenceException; /** - * Releases a database connection when it is no longer needed by the - * repository. + * Releases a database connection when it is no longer needed by the repository. * - * @param ident the database identifier used when obtaining this + * @param ident the database identifier used when obtaining this connection. + * @param readOnly the same value that was passed to {@link #getConnection} to obtain this * connection. - * @param readOnly the same value that was passed to {@link #getConnection} - * to obtain this connection. - * @param conn the connection to release (back into the pool or to be - * closed if pooling is not going on). + * @param conn the connection to release (back into the pool or to be closed if pooling is not + * going on). */ - public void releaseConnection (String ident, boolean readOnly, - Connection conn); + public void releaseConnection (String ident, boolean readOnly, Connection conn); /** - * Called by the repository if a failure occurred on the connection. - * It is expected that the connection will be disposed of and - * subsequent calls to getConnection will return a - * freshly established connection to the database. + * Called by the repository if a failure occurred on the connection. It is expected that the + * connection will be disposed of and subsequent calls to getConnection will + * return a freshly established connection to the database. * - * @param ident the database identifier used when obtaining this + * @param ident the database identifier used when obtaining this connection. + * @param readOnly the same value that was passed to {@link #getConnection} to obtain this * connection. - * @param readOnly the same value that was passed to {@link #getConnection} - * to obtain this connection. * @param conn the connection that failed. - * @param error the error thrown by the connection (which may be used - * to determine if the connection should be closed or can be reused). + * @param error the error thrown by the connection (which may be used to determine if the + * connection should be closed or can be reused). */ - public void connectionFailed (String ident, boolean readOnly, - Connection conn, SQLException error); + public void connectionFailed (String ident, boolean readOnly, Connection conn, + SQLException error); + + /** + * Returns the URL associated with this database identifier. This should be the same value that + * would be used if {@link #getConnection} were called. + */ + public String getURL (String ident); } diff --git a/src/java/com/samskivert/jdbc/DatabaseLiaison.java b/src/java/com/samskivert/jdbc/DatabaseLiaison.java index 102eb647..9b59fa4f 100644 --- a/src/java/com/samskivert/jdbc/DatabaseLiaison.java +++ b/src/java/com/samskivert/jdbc/DatabaseLiaison.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2001-2007 Michael Bayne -// +// // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or @@ -24,52 +24,144 @@ import java.sql.Connection; import java.sql.SQLException; /** - * Despite good intentions, JDBC and SQL do not provide a unified - * interface to all databases. There remain idiosyncrasies that must be - * worked around when making code interact with different database - * servers. The database liaison encapsulates the code needed to - * straighten out the curves and curve out the straights. + * Despite good intentions, JDBC and SQL do not provide a unified interface to all databases. There + * remain idiosyncrasies that must be worked around when making code interact with different + * database servers. The database liaison encapsulates the code needed to straighten out the curves + * and curve out the straights. */ public interface DatabaseLiaison { /** - * Indicates whether or not this database liaison is the proper - * liaison for the specified database URL. + * Indicates whether or not this database liaison is the proper liaison for the specified + * database URL. * - * @return true if we should use this liaison for connections created - * with the supplied URL, false if we should not. + * @return true if we should use this liaison for connections created with the supplied URL, + * false if we should not. */ public boolean matchesURL (String url); /** - * Determines whether or not the supplied SQL exception was caused by - * a duplicate row being inserted into a table with a unique key. + * Determines whether or not the supplied SQL exception was caused by a duplicate row being + * inserted into a table with a unique key. * - * @return true if the exception was caused by the insertion of a - * duplicate row, false if not. + * @return true if the exception was caused by the insertion of a duplicate row, false if not. */ public boolean isDuplicateRowException (SQLException sqe); /** - * Determines whether or not the supplied SQL exception is a transient - * failure, meaning one that is not related to the SQL being executed, - * but instead to the environment at the time of execution, like the - * connection to the database having been lost. + * Determines whether or not the supplied SQL exception is a transient failure, meaning one + * that is not related to the SQL being executed, but instead to the environment at the time of + * execution, like the connection to the database having been lost. * - * @return true if the exception was thrown due to a transient - * failure, false if not. + * @return true if the exception was thrown due to a transient failure, false if not. */ public boolean isTransientException (SQLException sqe); /** - * Returns the value of an AUTO_INCREMENT column for the - * last row insertion. This is MySQL specific, but exists here for now - * until we sort out a general purpose mechanism for assigning unique - * ids. + * Returns the value of an AUTO_INCREMENT column for the last row insertion. This + * is MySQL specific, but exists here for now until we sort out a general purpose mechanism for + * assigning unique ids. * - * @return the most recent ID generated by an insert into an - * AUTO_INCREMENT table or -1 if it could not be - * obtained. + * Note: More general purpose: {@link #lastInsertedId(Connection, String, String) + * + * @return the most recent ID generated by an insert into an AUTO_INCREMENT table + * or -1 if it could not be obtained. */ - public int lastInsertedId (Connection conn) throws SQLException; + @Deprecated + public int lastInsertedId (Connection conn) + throws SQLException; + + /** + * This method initializes the column value auto-generator described in {@link + * #lastInsertedId(Connection, String, String)}. + */ + public void initializeGenerator ( + Connection conn, String table, String column, int first, int step) + throws SQLException; + + /** + * This method attempts as dialect-agnostic an interface as possible to the ability of certain + * databases to auto-generated numerical values for i.e. key columns; there is MySQL's + * AUTO_INCREMENT and PostgreSQL's DEFAULT nextval(sequence), for example. + */ + public int lastInsertedId (Connection conn, String table, String column) + throws SQLException; + + /** + * Drops the given column from the given table. Returns true or false if the database did or + * did not report a schema modification. + */ + public boolean dropColumn (Connection conn, String table, String column) + throws SQLException; + + /** + * Adds a named index to a table on the given columns. Returns true or false if the database + * did or did not report a schema modification. + */ + public boolean addIndexToTable (Connection conn, String table, String[] columns, String index) + throws SQLException; + + /** + * Alter the name, but not the definition, of a given column on a given table. Returns true or + * false if the database did or did not report a schema modification. + */ + public boolean renameColumn (Connection conn, String table, String oldColumn, String newColumn) + throws SQLException; + + /** + * Alter the definition, but not the name, of a given column on a given table. Returns true or + * false if the database did or did not report a schema modification. + * + * TODO: We may wish to further split column definitions into type, nullability, etc. + */ + public boolean changeColumn ( + Connection conn, String table, String column, String definition) + throws SQLException; + + /** + * Created a new table of the given name with the given column names and column definitions; + * the given set of unique constraints (or null) and the given primary key columns (or null). + * Returns true if the table was successfully created, false if it already existed. + */ + public boolean createTableIfMissing ( + Connection conn, String table, String[] columns, String[] definitions, + String[][] uniqueConstraintColumns, String[] primaryKeyColumns) + throws SQLException; + + /** + * Returns true if the specified table exists and contains an index of the specified name; + * false if either conditions does not hold true. Note: the names are case sensitive. + */ + public boolean tableContainsIndex (Connection conn, String table, String index) + throws SQLException; + + /** + * Returns true if the specified table exists and contains a column with the specified name; + * false if either condition does not hold true. Note: the names are case sensitive. + */ + public boolean tableContainsColumn (Connection conn, String table, String column) + throws SQLException; + + /** + * Returns true if the table with the specified name exists, false if it does not. + * Note: the table name is case sensitive. + */ + public boolean tableExists (Connection conn, String name) throws SQLException; + + /** + * Returns the proper SQL to identify a table. Some databases require table names to be quoted. + */ + public String tableSQL (String table); + + /** + * Returns the proper SQL to identify a column. Some databases require columns names to be + * quoted. + */ + public String columnSQL (String column); + + /** + * Returns the proper SQL to identify an index. Some databases require index names to be + * quoted. + */ + public String indexSQL (String index); } diff --git a/src/java/com/samskivert/jdbc/DefaultLiaison.java b/src/java/com/samskivert/jdbc/DefaultLiaison.java index 748658d7..369e5823 100644 --- a/src/java/com/samskivert/jdbc/DefaultLiaison.java +++ b/src/java/com/samskivert/jdbc/DefaultLiaison.java @@ -24,33 +24,65 @@ import java.sql.Connection; import java.sql.SQLException; /** - * The default liaison is used if no other liaison could be matched for a - * particular database connection. It isn't very smart or useful but we - * need something. + * The default liaison is used if no other liaison could be matched for a particular database + * connection. It isn't very smart or useful but we need something. */ -public class DefaultLiaison implements DatabaseLiaison +public class DefaultLiaison extends BaseLiaison { - // documentation inherited + // from DatabaseLiaison public boolean matchesURL (String url) { return true; } - // documentation inherited + // from DatabaseLiaison public boolean isDuplicateRowException (SQLException sqe) { return false; } - // documentation inherited + // from DatabaseLiaison public boolean isTransientException (SQLException sqe) { return false; } - // documentation inherited + // from DatabaseLiaison public int lastInsertedId (Connection conn) throws SQLException { return -1; } + + // from DatabaseLiaison + public void initializeGenerator ( + Connection conn, String table, String column, int value, int size) + throws SQLException + { + // nothing doing + } + + // from DatabaseLiaison + public int lastInsertedId (Connection conn, String table, String column) + throws SQLException + { + return -1; + } + + // from DatabaseLiaison + public String columnSQL (String column) + { + return column; + } + + // from DatabaseLiaison + public String tableSQL (String table) + { + return table; + } + + // from DatabaseLiaison + public String indexSQL (String index) + { + return index; + } } diff --git a/src/java/com/samskivert/jdbc/JDBCUtil.java b/src/java/com/samskivert/jdbc/JDBCUtil.java index 7d8d1bf5..7364f21f 100644 --- a/src/java/com/samskivert/jdbc/JDBCUtil.java +++ b/src/java/com/samskivert/jdbc/JDBCUtil.java @@ -20,7 +20,6 @@ package com.samskivert.jdbc; -import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.Iterator; diff --git a/src/java/com/samskivert/jdbc/LiaisonRegistry.java b/src/java/com/samskivert/jdbc/LiaisonRegistry.java index f9641980..023848d8 100644 --- a/src/java/com/samskivert/jdbc/LiaisonRegistry.java +++ b/src/java/com/samskivert/jdbc/LiaisonRegistry.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2001-2007 Michael Bayne -// +// // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or @@ -23,30 +23,24 @@ package com.samskivert.jdbc; import java.sql.*; import java.util.ArrayList; -import java.util.Iterator; import java.util.HashMap; import com.samskivert.Log; /** - * The liaison registry provides access to the appropriate database - * liaison implementation for a particular database connection. + * The liaison registry provides access to the appropriate database liaison implementation for a + * particular database connection. */ public class LiaisonRegistry { /** - * Fetch the appropriate database liaison for the supplied database - * connection. + * Fetch the appropriate database liaison for the supplied URL, which should be the same string + * that would be used to configure a connection to the database. */ - public static DatabaseLiaison getLiaison (Connection conn) - throws SQLException + public static DatabaseLiaison getLiaison (String url) { - DatabaseMetaData dmd = conn.getMetaData(); - String url = dmd.getURL(); - // see if we already have a liaison mapped for this connection DatabaseLiaison liaison = _mappings.get(url); - if (liaison == null) { // scan the list looking for a matching liaison for (DatabaseLiaison candidate : _liaisons) { @@ -58,8 +52,8 @@ public class LiaisonRegistry // if we didn't find a matching liaison, use the default if (liaison == null) { - Log.warning("Unable to match liaison for database " + - "[url=" + url + "]. Using default."); + Log.warning("Unable to match liaison for database [url=" + url + "]. " + + "Using default."); liaison = new DefaultLiaison(); } @@ -70,25 +64,34 @@ public class LiaisonRegistry return liaison; } - protected static void registerLiaisonClass ( - Class lclass) + /** + * Fetch the appropriate database liaison for the supplied database connection. + */ + public static DatabaseLiaison getLiaison (Connection conn) + throws SQLException + { + return getLiaison(conn.getMetaData().getURL()); + + } + + protected static void registerLiaisonClass (Class lclass) { // create a new instance and stick it on our list try { _liaisons.add(lclass.newInstance()); } catch (Exception e) { - Log.warning("Unable to instantiate liaison " + - "[class=" + lclass.getName() + ", error=" + e + "]."); + Log.warning("Unable to instantiate liaison [class=" + lclass.getName() + + ", error=" + e + "]."); } } - protected static ArrayList _liaisons = - new ArrayList(); + protected static ArrayList _liaisons = new ArrayList(); protected static HashMap _mappings = new HashMap(); // register our liaison classes static { registerLiaisonClass(MySQLLiaison.class); + registerLiaisonClass(PostgreSQLLiaison.class); } } diff --git a/src/java/com/samskivert/jdbc/MySQLLiaison.java b/src/java/com/samskivert/jdbc/MySQLLiaison.java index 7be3286f..66e21c86 100644 --- a/src/java/com/samskivert/jdbc/MySQLLiaison.java +++ b/src/java/com/samskivert/jdbc/MySQLLiaison.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2001-2007 Michael Bayne -// +// // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or @@ -22,25 +22,28 @@ package com.samskivert.jdbc; import java.sql.*; +import com.samskivert.Log; +import com.samskivert.util.StringUtil; + /** * A database liaison for the MySQL database. */ -public class MySQLLiaison implements DatabaseLiaison +public class MySQLLiaison extends BaseLiaison { - // documentation inherited + // from DatabaseLiaison public boolean matchesURL (String url) { return url.startsWith("jdbc:mysql"); } - // documentation inherited + // from DatabaseLiaison public boolean isDuplicateRowException (SQLException sqe) { String msg = sqe.getMessage(); return (msg != null && msg.indexOf("Duplicate entry") != -1); } - // documentation inherited + // from DatabaseLiaison public boolean isTransientException (SQLException sqe) { String msg = sqe.getMessage(); @@ -50,23 +53,84 @@ public class MySQLLiaison implements DatabaseLiaison msg.indexOf("Broken pipe") != -1)); } - // documentation inherited + // from DatabaseLiaison public int lastInsertedId (Connection conn) throws SQLException { - Statement stmt = null; + return lastInsertedId(conn, null, null); + } - // we have to do this by hand. alas all is not roses. + // from DatabaseLiaison + public void initializeGenerator ( + Connection conn, String table, String column, int first, int step) + throws SQLException + { + // AUTO_INCREMENT does not allow this kind of control + } + + // from DatabaseLiaison + public int lastInsertedId (Connection conn, String table, String column) throws SQLException + { + // MySQL does not keep track of per-table-and-column insertion data, so we are pretty much + // going on blind faith here that we're fetching the right ID. In the overwhelming number + // of cases that will be so, but it's still not pretty. + Statement stmt = null; try { stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()"); - if (rs.next()) { - return rs.getInt(1); - } else { - return -1; - } - + return rs.next() ? rs.getInt(1) : -1; } finally { JDBCUtil.close(stmt); } } + + @Override // from BaseLiaison + public boolean addIndexToTable (Connection conn, String table, String[] columns, String ixName) + throws SQLException + { + if (tableContainsIndex(conn, table, ixName)) { + return false; + } + ixName = (ixName != null ? ixName : StringUtil.join(columns, "_")); + + // MySQL's "CREATE INDEX" is buggy, it actually changes the case of the table names you're + // working with. Luckily (?) ALTER TABLE ADD INDEX works, so we do that here. + StringBuilder update = new StringBuilder("ALTER TABLE ").append(table). + append(" ADD INDEX ").append(indexSQL(ixName)).append(" ("); + for (int ii = 0; ii < columns.length; ii ++) { + if (ii > 0) { + update.append(", "); + } + update.append(columnSQL(columns[ii])); + } + update.append(")"); + + PreparedStatement stmt = null; + try { + stmt = conn.prepareStatement(update.toString()); + stmt.executeUpdate(); + } finally { + JDBCUtil.close(stmt); + } + + Log.info("Database index '" + ixName + "' added to table '" + table + "'"); + return true; + } + + // from DatabaseLiaison + public String columnSQL (String column) + { + return column; + } + + // from DatabaseLiaison + public String tableSQL (String table) + { + return table; + } + + // from DatabaseLiaison + public String indexSQL (String index) + { + return index; + } } diff --git a/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java b/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java new file mode 100644 index 00000000..413027eb --- /dev/null +++ b/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java @@ -0,0 +1,113 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc; + +import java.sql.*; + +/** + * A database liaison for the MySQL database. + */ +public class PostgreSQLLiaison extends BaseLiaison +{ + // from DatabaseLiaison + public boolean matchesURL (String url) + { + return url.startsWith("jdbc:postgresql"); + } + + // from DatabaseLiaison + public boolean isDuplicateRowException (SQLException sqe) + { + String msg = sqe.getMessage(); + return (msg != null && msg.indexOf("duplicate key") != -1); + } + + // from DatabaseLiaison + public boolean isTransientException (SQLException sqe) + { + // TODO: Add more error messages here as we encounter them. + String msg = sqe.getMessage(); + return (msg != null && + msg.indexOf("An I/O error occured while sending to the backend") != -1); + } + + // from DatabaseLiaison + public int lastInsertedId (Connection conn) throws SQLException + { + throw new SQLException( + "No backwards-compatible lastInsertedId() support for PostgreSQL in this code."); + } + + // from DatabaseLiaison + public int lastInsertedId (Connection conn, String table, String column) throws SQLException + { + // PostgreSQL's support for auto-generated ID's comes in the form of appropriately named + // sequences and DEFAULT nextval(sequence) modifiers in the ID columns. To get the next ID, + // we use the currval() method which is set in a database sessions when any given sequence + // is incremented. + Statement stmt = null; + + try { + stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery( + "select currval('\"" + table + "_" + column + "_seq\"')"); + if (rs.next()) { + return rs.getInt(1); + } else { + return -1; + } + + } finally { + JDBCUtil.close(stmt); + } + } + + public void initializeGenerator ( + Connection conn, String table, String column, int first, int step) + throws SQLException + { + Statement stmt = conn.createStatement(); + try { + stmt.executeUpdate("alter sequence \"" + table + "_" + column + "_seq\" " + + " restart with " + first + " increment " + step); + } finally { + JDBCUtil.close(stmt); + } + } + + // from DatabaseLiaison + public String columnSQL (String column) + { + return "\"" + column + "\""; + } + + // from DatabaseLiaison + public String tableSQL (String table) + { + return "\"" + table + "\""; + } + + // from DatabaseLiaison + public String indexSQL (String index) + { + return "\"" + index + "\""; + } +} diff --git a/src/java/com/samskivert/jdbc/SimpleRepository.java b/src/java/com/samskivert/jdbc/SimpleRepository.java index 90b9ff16..71fda3d1 100644 --- a/src/java/com/samskivert/jdbc/SimpleRepository.java +++ b/src/java/com/samskivert/jdbc/SimpleRepository.java @@ -27,9 +27,8 @@ import com.samskivert.io.PersistenceException; import com.samskivert.util.StringUtil; /** - * The simple repository should be used for a repository that only needs - * access to a single JDBC connection instance to perform its persistence - * services. + * The simple repository should be used for a repository that only needs access to a single JDBC + * connection instance to perform its persistence services. */ public class SimpleRepository extends Repository { @@ -41,12 +40,10 @@ public class SimpleRepository extends Repository } /** - * Configures an operation that will be invoked prior to the execution - * of every database operation to validate whether some pre-condition - * is met. This mainly exists for systems that wish to ensure that all - * database operations take place on a particular thread (or not on a - * particular thread) as database operations are generally slow and - * blocking. + * Configures an operation that will be invoked prior to the execution of every database + * operation to validate whether some pre-condition is met. This mainly exists for systems that + * wish to ensure that all database operations take place on a particular thread (or not on a + * particular thread) as database operations are generally slow and blocking. */ public static void setExecutePreCondition (PreCondition condition) { @@ -54,14 +51,13 @@ public class SimpleRepository extends Repository } /** - * Creates and initializes a simple repository which will access the - * database identified by the supplied database identifier. + * Creates and initializes a simple repository which will access the database identified by the + * supplied database identifier. * - * @param provider the connection provider which will be used to - * obtain our database connection. - * @param dbident the identifier of the database that will be accessed by - * this repository or null if the derived class will call {@link - * #configureDatabaseIdent} by hand later. + * @param provider the connection provider which will be used to obtain our database + * connection. + * @param dbident the identifier of the database that will be accessed by this repository or + * null if the derived class will call {@link #configureDatabaseIdent} by hand later. */ public SimpleRepository (ConnectionProvider provider, String dbident) { @@ -73,18 +69,17 @@ public class SimpleRepository extends Repository } /** - * This is called automatically if a dbident is provided at construct time, - * but a derived class can pass null to its constructor and then call this - * method itself later if it wishes to obtain its database identifier from - * an overridable method which could not otherwise be called at construct - * time. + * This is called automatically if a dbident is provided at construct time, but a derived class + * can pass null to its constructor and then call this method itself later if it wishes to + * obtain its database identifier from an overridable method which could not otherwise be + * called at construct time. */ protected void configureDatabaseIdent (String dbident) { _dbident = dbident; - // give the repository a chance to do any schema migration before - // things get further underway + // give the repository a chance to do any schema migration before things get further + // underway try { executeUpdate(new Operation() { public Object invoke (Connection conn, DatabaseLiaison liaison) @@ -101,9 +96,8 @@ public class SimpleRepository extends Repository } /** - * Executes the supplied read-only operation. In the event of a transient - * failure, the repository will attempt to reestablish the database - * connection and try the operation again. + * Executes the supplied read-only operation. In the event of a transient failure, the + * repository will attempt to reestablish the database connection and try the operation again. * * @return whatever value is returned by the invoked operation. */ @@ -114,9 +108,8 @@ public class SimpleRepository extends Repository } /** - * Executes the supplied read-write operation. In the event of a transient - * failure, the repository will attempt to reestablish the database - * connection and try the operation again. + * Executes the supplied read-write operation. In the event of a transient failure, the + * repository will attempt to reestablish the database connection and try the operation again. * * @return whatever value is returned by the invoked operation. */ @@ -127,22 +120,18 @@ public class SimpleRepository extends Repository } /** - * Executes the supplied operation followed by a call to - * commit() on the connection unless a - * PersistenceException or runtime error occurs, in which - * case a call to rollback() is executed on the - * connection. + * Executes the supplied operation followed by a call to commit() on the + * connection unless a PersistenceException or runtime error occurs, in which case + * a call to rollback() is executed on the connection. * - * @param retryOnTransientFailure if true and the operation fails due to a - * transient failure (like losing the connection to the database or - * deadlock detection), the connection to the database will be - * reestablished (if necessary) and the operation attempted once more. + * @param retryOnTransientFailure if true and the operation fails due to a transient failure + * (like losing the connection to the database or deadlock detection), the connection to the + * database will be reestablished (if necessary) and the operation attempted once more. * @param readOnly whether or not to request a read-only connection. * * @return whatever value is returned by the invoked operation. */ - protected V execute (Operation op, boolean retryOnTransientFailure, - boolean readOnly) + protected V execute (Operation op, boolean retryOnTransientFailure, boolean readOnly) throws PersistenceException { Connection conn = null; @@ -153,16 +142,16 @@ public class SimpleRepository extends Repository // check our pre-condition if (_precond != null && !_precond.validate(_dbident, op)) { - Log.warning("Repository operation failed pre-condition check! " + - "[dbident=" + _dbident + ", op=" + op + "]."); + Log.warning("Repository operation failed pre-condition check! [dbident=" + _dbident + + ", op=" + op + "]."); Thread.dumpStack(); } // obtain our database connection and associated goodies conn = _provider.getConnection(_dbident, readOnly); - // make sure that no one else performs a database operation using the - // same connection until we're done + // make sure that no one else performs a database operation using the same connection until + // we're done synchronized (conn) { try { liaison = LiaisonRegistry.getLiaison(conn); @@ -193,15 +182,15 @@ public class SimpleRepository extends Repository } catch (SQLException sqe) { if (attemptedOperation) { - // back out our changes if something got hosed (but not if - // the hosage was a result of losing our connection) + // back out our changes if something got hosed (but not if the hosage was a + // result of losing our connection) try { if (supportsTransactions && !conn.isClosed()) { conn.rollback(); } } catch (SQLException rbe) { - Log.warning("Unable to roll back operation " + - "[err=" + sqe + ", rberr=" + rbe + "]."); + Log.warning("Unable to roll back operation [err=" + sqe + + ", rberr=" + rbe + "]."); } } @@ -218,13 +207,11 @@ public class SimpleRepository extends Repository throw new PersistenceException(err, sqe); } - // the MySQL JDBC driver has the annoying habit of including - // the embedded exception stack trace in the message of their - // outer exception; if I want a fucking stack trace, I'll call - // printStackTrace() thanksverymuch + // the MySQL JDBC driver has the annoying habit of including the embedded exception + // stack trace in the message of their outer exception; if I want a fucking stack + // trace, I'll call printStackTrace() thanksverymuch String msg = StringUtil.split("" + sqe, "\n")[0]; - Log.info("Transient failure executing operation, " + - "retrying [error=" + msg + "]."); + Log.info("Transient failure executing operation, retrying [error=" + msg + "]."); } catch (PersistenceException pe) { // back out our changes if something got hosed @@ -233,8 +220,8 @@ public class SimpleRepository extends Repository conn.rollback(); } } catch (SQLException rbe) { - Log.warning("Unable to roll back operation " + - "[origerr=" + pe + ", rberr=" + rbe + "]."); + Log.warning("Unable to roll back operation [origerr=" + pe + + ", rberr=" + rbe + "]."); } throw pe; @@ -246,8 +233,8 @@ public class SimpleRepository extends Repository conn.rollback(); } } catch (SQLException rbe) { - Log.warning("Unable to roll back operation " + - "[origerr=" + rte + ", rberr=" + rbe + "]."); + Log.warning("Unable to roll back operation [origerr=" + rte + + ", rberr=" + rbe + "]."); } throw rte; @@ -259,15 +246,15 @@ public class SimpleRepository extends Repository } } - // we'll only fall through here if the above code failed due to a - // transient exception (the connection was closed for being idle, for - // example) and we've been asked to retry; so let's do so + // we'll only fall through here if the above code failed due to a transient exception (the + // connection was closed for being idle, for example) and we've been asked to retry; so + // let's do so return execute(op, false, readOnly); } /** - * Executes the supplied update query in this repository, returning - * the number of rows modified. + * Executes the supplied update query in this repository, returning the number of rows + * modified. */ protected int update (final String query) throws PersistenceException @@ -288,9 +275,8 @@ public class SimpleRepository extends Repository } /** - * Executes the supplied update query in this repository, throwing an - * exception if the modification count is not equal to the specified - * count. + * Executes the supplied update query in this repository, throwing an exception if the + * modification count is not equal to the specified count. */ protected void checkedUpdate (final String query, final int count) throws PersistenceException @@ -312,8 +298,8 @@ public class SimpleRepository extends Repository } /** - * Executes the supplied update query in this repository, logging a warning - * if the modification count is not equal to the specified count. + * Executes the supplied update query in this repository, logging a warning if the modification + * count is not equal to the specified count. */ protected void warnedUpdate (final String query, final int count) throws PersistenceException @@ -335,15 +321,12 @@ public class SimpleRepository extends Repository } /** - * Instructs MySQL to perform table maintenance on the specified - * table. + * Instructs MySQL to perform table maintenance on the specified table. * - * @param action analyze recomputes the distribution of - * the keys for the specified table. This can help certain joins to be - * performed more efficiently. optimize instructs MySQL - * to coalesce fragmented records and reclaim space left by deleted - * records. This can improve a tables efficiency but can take a long - * time to run on large tables. + * @param action analyze recomputes the distribution of the keys for the specified + * table. This can help certain joins to be performed more efficiently. optimize + * instructs MySQL to coalesce fragmented records and reclaim space left by deleted records. + * This can improve a tables efficiency but can take a long time to run on large tables. */ protected void maintenance (final String action, final String table) throws PersistenceException @@ -355,15 +338,12 @@ public class SimpleRepository extends Repository Statement stmt = null; try { stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery( - action + " table " + table); + ResultSet rs = stmt.executeQuery(action + " table " + table); while (rs.next()) { String result = rs.getString("Msg_text"); if (result == null || - result.indexOf("up to date") == -1 && - !result.equals("OK")) { - Log.info("Table maintenance [" + - SimpleRepository.toString(rs) + "]."); + (result.indexOf("up to date") == -1 && !result.equals("OK"))) { + Log.info("Table maintenance [" + SimpleRepository.toString(rs) + "]."); } } @@ -376,12 +356,10 @@ public class SimpleRepository extends Repository } /** - * Derived classes can override this method and perform any schema - * migration they might need (using the idempotent {@link JDBCUtil} schema - * migration methods). This is called during the repository's constructor - * and will thus take place before derived classes (like the {@link - * JORARepository} introspect on the schema to match it up to associated - * Java classes). + * Derived classes can override this method and perform any schema migration they might need + * (using the idempotent {@link JDBCUtil} schema migration methods). This is called during the + * repository's constructor and will thus take place before derived classes (like the {@link + * JORARepository} introspect on the schema to match it up to associated Java classes). */ protected void migrateSchema (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException @@ -389,17 +367,17 @@ public class SimpleRepository extends Repository } /** - * Called when we fetch a connection from the provider. This gives - * derived classes an opportunity to configure whatever internals they - * might be using with the connection that was fetched. + * Called when we fetch a connection from the provider. This gives derived classes an + * opportunity to configure whatever internals they might be using with the connection that was + * fetched. */ protected void gotConnection (Connection conn) { } /** - * Converts a row of a result set to a string, prepending each column - * with the column name from the result set metadata. + * Converts a row of a result set to a string, prepending each column with the column name from + * the result set metadata. */ protected static String toString (ResultSet rs) throws SQLException @@ -418,5 +396,6 @@ public class SimpleRepository extends Repository } protected String _dbident; + protected static PreCondition _precond; } diff --git a/src/java/com/samskivert/jdbc/StaticConnectionProvider.java b/src/java/com/samskivert/jdbc/StaticConnectionProvider.java index 1ac9dcd9..4d77d967 100644 --- a/src/java/com/samskivert/jdbc/StaticConnectionProvider.java +++ b/src/java/com/samskivert/jdbc/StaticConnectionProvider.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2001-2007 Michael Bayne -// +// // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or @@ -31,15 +31,12 @@ import com.samskivert.util.PropertiesUtil; import com.samskivert.util.StringUtil; /** - * The static connection provider generates JDBC connections based on - * configuration information provided via a properties file. It does no - * connection pooling and always returns the same connection for a - * particular identifier (unless that connection need be closed because of - * a connection failure, in which case it opens a new one the next time - * the connection is requested). + * The static connection provider generates JDBC connections based on configuration information + * provided via a properties file. It does no connection pooling and always returns the same + * connection for a particular identifier (unless that connection need be closed because of a + * connection failure, in which case it opens a new one the next time the connection is requested). * - *

The configuration properties file should contain the following - * information: + *

The configuration properties file should contain the following information: * *

  * IDENT.driver=[jdbc driver class]
@@ -50,14 +47,13 @@ import com.samskivert.util.StringUtil;
  * [...]
  * 
* - * Where IDENT is the database identifier for a particular - * database connection. When a particular database identifier is - * requested, the configuration information will be fetched from the - * properties. + * Where IDENT is the database identifier for a particular database connection. When a + * particular database identifier is requested, the configuration information will be fetched from + * the properties. * - *

Additionally, a default set of properties can be provided using the - * identifier default. Values not provided for a specific - * identifier will be sought from the defaults. For example: + *

Additionally, a default set of properties can be provided using the identifier + * default. Values not provided for a specific identifier will be sought from the + * defaults. For example: * *

  * default.driver=[jdbc driver class]
@@ -75,15 +71,15 @@ import com.samskivert.util.StringUtil;
 public class StaticConnectionProvider implements ConnectionProvider
 {
     /**
-     * Constructs a static connection provider which will load its
-     * configuration from a properties file accessible via the classpath
-     * of the running application and identified by the specified path.
+     * Constructs a static connection provider which will load its configuration from a properties
+     * file accessible via the classpath of the running application and identified by the specified
+     * path.
      *
-     * @param propPath the path (relative to the classpath) to the
-     * properties file that will be used for configuration information.
+     * @param propPath the path (relative to the classpath) to the properties file that will be
+     * used for configuration information.
      *
-     * @exception IOException thrown if an error occurs locating or
-     * loading the specified properties file.
+     * @exception IOException thrown if an error occurs locating or loading the specified
+     * properties file.
      */
     public StaticConnectionProvider (String propPath)
         throws IOException
@@ -92,8 +88,8 @@ public class StaticConnectionProvider implements ConnectionProvider
     }
 
     /**
-     * Constructs a static connection provider which will fetch its
-     * configuration information from the specified properties object.
+     * Constructs a static connection provider which will fetch its configuration information from
+     * the specified properties object.
      *
      * @param props the configuration for this connection provider.
      */
@@ -103,8 +99,7 @@ public class StaticConnectionProvider implements ConnectionProvider
     }
 
     /**
-     * Closes all of the open database connections in preparation for
-     * shutting down.
+     * Closes all of the open database connections in preparation for shutting down.
      */
     public void shutdown ()
     {
@@ -112,12 +107,11 @@ public class StaticConnectionProvider implements ConnectionProvider
         Iterator iter = _keys.keySet().iterator();
         while (iter.hasNext()) {
             String key = (String)iter.next();
-            Mapping conmap = (Mapping)_keys.get(key);
+            Mapping conmap = _keys.get(key);
             try {
                 conmap.connection.close();
             } catch (SQLException sqe) {
-                Log.warning("Error shutting down connection " +
-                            "[key=" + key + ", err=" + sqe + "].");
+                Log.warning("Error shutting down connection [key=" + key + ", err=" + sqe + "].");
             }
         }
 
@@ -126,6 +120,13 @@ public class StaticConnectionProvider implements ConnectionProvider
         _idents.clear();
     }
 
+    // from ConnectionProvider
+    public String getURL (String ident)
+    {
+        Properties props = PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
+        return props.getProperty("url");
+    }
+
     // documentation inherited
     public Connection getConnection (String ident, boolean readOnly)
         throws PersistenceException
@@ -135,9 +136,7 @@ public class StaticConnectionProvider implements ConnectionProvider
 
         // open the connection if we haven't already
         if (conmap == null) {
-            String[] defaults;
-            Properties props =
-                PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
+            Properties props = PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
 
             // get the JDBC configuration info
             String err = "No driver class specified [ident=" + ident + "].";
@@ -149,33 +148,30 @@ public class StaticConnectionProvider implements ConnectionProvider
             err = "No driver password specified [ident=" + ident + "].";
             String password = requireProp(props, "password", err);
 
-            // if this is a read-only connection,
-
-            // we cache connections by username+url+readOnly to avoid making
-            // more that one connection to a particular database server
+            // if this is a read-only connection, we cache connections by username+url+readOnly to
+            // avoid making more that one connection to a particular database server
             String key = username + "@" + url + ":" + readOnly;
             conmap = _keys.get(key);
             if (conmap == null) {
                 Log.debug("Creating " + key + " for " + ident + ".");
                 conmap = new Mapping();
                 conmap.key = key;
-                conmap.connection =
-                    openConnection(driver, url, username, password);
+                conmap.connection = openConnection(driver, url, username, password);
 
-                // make the connection read-only to let the JDBC driver know
-                // that it can and should use the read-only mirror(s)
+                // make the connection read-only to let the JDBC driver know that it can and should
+                // use the read-only mirror(s)
                 if (readOnly) {
                     try {
                         conmap.connection.setReadOnly(true);
                     } catch (SQLException sqe) {
                         closeConnection(ident, conmap.connection);
-                        err = "Failed to make connection read-only " +
-                            "[key=" + key + ", ident=" + ident + "].";
+                        err = "Failed to make connection read-only [key=" + key +
+                            ", ident=" + ident + "].";
                         throw new PersistenceException(err, sqe);
                     }
                 }
-
                 _keys.put(key, conmap);
+
             } else {
                 Log.debug("Reusing " + key + " for " + ident + ".");
             }
@@ -185,19 +181,25 @@ public class StaticConnectionProvider implements ConnectionProvider
             _idents.put(mapkey, conmap);
         }
 
+        // in case the previous user turned off auto-commit we have to make sure here it's on
+        try {
+            conmap.connection.setAutoCommit(true);
+        } catch (SQLException e) {
+            throw new PersistenceException("Failed to turn on connection auto-commit", e);
+        }
+
         return conmap.connection;
     }
 
     // documentation inherited
-    public void releaseConnection (String ident, boolean readOnly,
-                                   Connection conn)
+    public void releaseConnection (String ident, boolean readOnly, Connection conn)
     {
         // nothing to do here, all is well
     }
 
     // documentation inherited
-    public void connectionFailed (String ident, boolean readOnly,
-                                  Connection conn, SQLException error)
+    public void connectionFailed (
+        String ident, boolean readOnly, Connection conn, SQLException error)
     {
         String mapkey = ident + ":" + readOnly;
         Mapping conmap = _idents.get(mapkey);
@@ -216,8 +218,7 @@ public class StaticConnectionProvider implements ConnectionProvider
         _keys.remove(conmap.key);
     }
 
-    protected Connection openConnection (
-        String driver, String url, String username, String password)
+    protected Connection openConnection (String driver, String url, String username, String passwd)
         throws PersistenceException
     {
         // create an instance of the driver
@@ -233,11 +234,11 @@ public class StaticConnectionProvider implements ConnectionProvider
         try {
             Properties props = new Properties();
             props.put("user", username);
-            props.put("password", password);
+            props.put("password", passwd);
             return jdriver.connect(url, props);
+
         } catch (SQLException sqe) {
-            String err = "Error creating database connection " +
-                "[driver=" + driver + ", url=" + url +
+            String err = "Error creating database connection [driver=" + driver + ", url=" + url +
                 ", username=" + username + "].";
             throw new PersistenceException(err, sqe);
         }
@@ -253,25 +254,23 @@ public class StaticConnectionProvider implements ConnectionProvider
         }
     }
 
-    protected static String requireProp (
-        Properties props, String name, String errmsg)
+    protected static String requireProp (Properties props, String name, String errmsg)
 	throws PersistenceException
     {
 	String value = props.getProperty(name);
 	if (StringUtil.isBlank(value)) {
-            // augment the error message
-            errmsg = "Unable to get connection. " + errmsg;
+            errmsg = "Unable to get connection. " + errmsg; // augment the error message
 	    throw new PersistenceException(errmsg);
 	}
 	return value;
     }
 
-    /** Contains information on a particular connection to which any
-     * number of database identifiers can be mapped. */
+    /** Contains information on a particular connection to which any number of database identifiers
+     * can be mapped. */
     protected static class Mapping
     {
-        /** The combination of username and JDBC url that uniquely
-         * identifies our database connection. */
+        /** The combination of username and JDBC url that uniquely identifies our database
+         * connection. */
         public String key;
 
         /** The connection itself. */
diff --git a/src/java/com/samskivert/jdbc/TransitionRepository.java b/src/java/com/samskivert/jdbc/TransitionRepository.java
index 55b99592..b8ac406d 100644
--- a/src/java/com/samskivert/jdbc/TransitionRepository.java
+++ b/src/java/com/samskivert/jdbc/TransitionRepository.java
@@ -3,7 +3,7 @@
 //
 // samskivert library - useful routines for java programs
 // Copyright (C) 2001-2007 Michael Bayne
-// 
+//
 // This library is free software; you can redistribute it and/or modify it
 // under the terms of the GNU Lesser General Public License as published
 // by the Free Software Foundation; either version 2.1 of the License, or
@@ -28,9 +28,8 @@ import java.sql.SQLException;
 import com.samskivert.io.PersistenceException;
 
 /**
- * Used to note that transitionary code has been run to migrate persistent
- * data. The TransitionRepository is especially useful for data that
- * one cannot examine to determine if it's been transitioned.
+ * Used to note that transitionary code has been run to migrate persistent data. This is especially
+ * useful for data that one cannot examine to determine if it's been transitioned.
  */
 public class TransitionRepository extends SimpleRepository
 {
@@ -81,18 +80,20 @@ public class TransitionRepository extends SimpleRepository
             public Boolean invoke (Connection conn, DatabaseLiaison liaison)
                 throws SQLException, PersistenceException
             {
-                Object found = null;
                 PreparedStatement stmt = null;
                 try {
-                    stmt = conn.prepareStatement("select NAME " +
-                        "from TRANSITIONS where CLASS=? and NAME=?");
+                    stmt = conn.prepareStatement(
+                        " select " + liaison.columnSQL("NAME") +
+                        "   from " + liaison.tableSQL("TRANSITIONS") +
+                        "  where " + liaison.columnSQL("CLASS") + "=?" +
+                        "    and " + liaison.columnSQL("NAME") + "=?");
                     stmt.setString(1, cname);
                     stmt.setString(2, name);
                     ResultSet rs = stmt.executeQuery();
                     if (rs.next()) {
-                        while (rs.next()); // is this really necessary?
                         return true;
                     }
+
                 } finally {
                     JDBCUtil.close(stmt);
                 }
@@ -114,12 +115,14 @@ public class TransitionRepository extends SimpleRepository
             {
                 PreparedStatement stmt = null;
                 try {
-                    stmt = conn.prepareStatement("insert into TRANSITIONS " +
-                        "(CLASS, NAME) values (?, ?)");
+                    stmt = conn.prepareStatement(
+                        "insert into " + liaison.tableSQL("TRANSITIONS") + " (" +
+                        liaison.columnSQL("CLASS") + ", " + liaison.columnSQL("NAME") +
+                        ") values (?, ?)");
                     stmt.setString(1, cname);
                     stmt.setString(2, name);
-
                     JDBCUtil.checkedUpdate(stmt, 1);
+
                 } finally {
                     JDBCUtil.close(stmt);
                 }
@@ -141,8 +144,10 @@ public class TransitionRepository extends SimpleRepository
             {
                 PreparedStatement stmt = null;
                 try {
-                    stmt = conn.prepareStatement("delete from TRANSITIONS " +
-                        "where CLASS=? and NAME=?");
+                    stmt = conn.prepareStatement(
+                        " delete from " + liaison.tableSQL("TRANSITIONS") +
+                        "       where " + liaison.columnSQL("CLASS") + "=? " +
+                        "         and " + liaison.columnSQL("NAME") + "=?");
                     stmt.setString(1, cname);
                     stmt.setString(2, name);
 
@@ -160,10 +165,12 @@ public class TransitionRepository extends SimpleRepository
     protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
         throws SQLException, PersistenceException
     {
-        JDBCUtil.createTableIfMissing(conn, "TRANSITIONS", new String[] {
-            "CLASS varchar(200) not null",
-            "NAME varchar(50) not null",
-            "APPLIED timestamp not null",
-            "primary key (CLASS, NAME)" }, "");
+        liaison.createTableIfMissing(
+            conn,
+            "TRANSITIONS",
+            new String[] { "CLASS", "NAME", "APPLIED" },
+            new String[] { "VARCHAR(200)", "VARCHAR(50)", "TIMESTAMP NOT NULL" },
+            null,
+            new String[] { "CLASS", "NAME" });
     }
 }