Allow schema upgrades to be logged and/or bypassed

Change-Id: Icd449ccb44a21d3d1856d96ce23cf49cffa43fa4
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtorm/client/Schema.java b/src/main/java/com/google/gwtorm/client/Schema.java
index 09c4ba9..2739177 100644
--- a/src/main/java/com/google/gwtorm/client/Schema.java
+++ b/src/main/java/com/google/gwtorm/client/Schema.java
@@ -62,18 +62,20 @@
    * for applications to continue to query after the update. Any unused columns
    * that are NOT NULL are altered to accept NULL.
    *
+   * @param e executor to perform (or log) the statements.
    * @throws OrmException one or more objects could not be added to the schema.
    */
-  void updateSchema() throws OrmException;
+  void updateSchema(StatementExecutor e) throws OrmException;
 
   /**
    * Drop any unused columns, tables, or sequences.
    * <p>
    * This method destroys data, as columns may be removed entirely.
    *
+   * @param e executor to perform (or log) the statements.
    * @throws OrmException one or more drops could not be completed.
    */
-  void pruneSchema() throws OrmException;
+  void pruneSchema(StatementExecutor e) throws OrmException;
 
   /**
    * Begin a new transaction.
diff --git a/src/main/java/com/google/gwtorm/client/StatementExecutor.java b/src/main/java/com/google/gwtorm/client/StatementExecutor.java
new file mode 100644
index 0000000..3db725f
--- /dev/null
+++ b/src/main/java/com/google/gwtorm/client/StatementExecutor.java
@@ -0,0 +1,20 @@
+// Copyright 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+/** Executes statements against the schema. */
+public interface StatementExecutor {
+  void execute(String sql) throws OrmException;
+}
diff --git a/src/main/java/com/google/gwtorm/jdbc/JdbcExecutor.java b/src/main/java/com/google/gwtorm/jdbc/JdbcExecutor.java
new file mode 100644
index 0000000..8e553a6
--- /dev/null
+++ b/src/main/java/com/google/gwtorm/jdbc/JdbcExecutor.java
@@ -0,0 +1,55 @@
+// Copyright 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.StatementExecutor;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class JdbcExecutor implements StatementExecutor {
+  private final Statement stmt;
+
+  public JdbcExecutor(final JdbcSchema schema) throws OrmException {
+    this(schema.getConnection());
+  }
+
+  public JdbcExecutor(final Connection c) throws OrmException {
+    try {
+      stmt = c.createStatement();
+    } catch (SQLException e) {
+      throw new OrmException("Cannot create statement for executor", e);
+    }
+  }
+
+  @Override
+  public void execute(String sql) throws OrmException {
+    try {
+      stmt.execute(sql);
+    } catch (SQLException e) {
+      throw new OrmException("Cannot apply SQL\n" + sql, e);
+    }
+  }
+
+  public void close() {
+    try {
+      stmt.close();
+    } catch (SQLException e) {
+      //
+    }
+  }
+}
diff --git a/src/main/java/com/google/gwtorm/jdbc/JdbcSchema.java b/src/main/java/com/google/gwtorm/jdbc/JdbcSchema.java
index 04640fb..a587cf1 100644
--- a/src/main/java/com/google/gwtorm/jdbc/JdbcSchema.java
+++ b/src/main/java/com/google/gwtorm/jdbc/JdbcSchema.java
@@ -18,6 +18,7 @@
 import com.google.gwtorm.client.OrmException;
 import com.google.gwtorm.client.OrmRunnable;
 import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.client.StatementExecutor;
 import com.google.gwtorm.client.Transaction;
 import com.google.gwtorm.schema.ColumnModel;
 import com.google.gwtorm.schema.RelationModel;
@@ -71,70 +72,59 @@
     }
   }
 
-  public void updateSchema() throws OrmException {
+  public void updateSchema(final StatementExecutor e) throws OrmException {
     try {
-      createSequences();
-      createRelations();
+      createSequences(e);
+      createRelations(e);
 
       for (final RelationModel rel : dbDef.getSchemaModel().getRelations()) {
-        addColumns(rel);
+        addColumns(e, rel);
       }
-    } catch (SQLException e) {
-      throw new OrmException("Schema update failure", e);
+    } catch (SQLException err) {
+      throw new OrmException("Cannot update schema", err);
     }
   }
 
-  private void createSequences() throws SQLException {
+  private void createSequences(final StatementExecutor e) throws OrmException,
+      SQLException {
     final SqlDialect dialect = dbDef.getDialect();
     final SchemaModel model = dbDef.getSchemaModel();
-    final Statement stmt = getConnection().createStatement();
-    try {
-      Set<String> have = dialect.listSequences(getConnection());
-      for (final SequenceModel s : model.getSequences()) {
-        if (!have.contains(s.getSequenceName().toLowerCase())) {
-          stmt.execute(s.getCreateSequenceSql(dialect));
-        }
+
+    Set<String> have = dialect.listSequences(getConnection());
+    for (final SequenceModel s : model.getSequences()) {
+      if (!have.contains(s.getSequenceName().toLowerCase())) {
+        e.execute(s.getCreateSequenceSql(dialect));
       }
-    } finally {
-      stmt.close();
     }
   }
 
-  private void createRelations() throws SQLException {
+  private void createRelations(final StatementExecutor e) throws SQLException,
+      OrmException {
     final SqlDialect dialect = dbDef.getDialect();
     final SchemaModel model = dbDef.getSchemaModel();
-    final Statement stmt = getConnection().createStatement();
-    try {
-      Set<String> have = dialect.listTables(getConnection());
-      for (final RelationModel r : model.getRelations()) {
-        if (!have.contains(r.getRelationName().toLowerCase())) {
-          stmt.execute(r.getCreateTableSql(dialect));
-        }
+    Set<String> have = dialect.listTables(getConnection());
+    for (final RelationModel r : model.getRelations()) {
+      if (!have.contains(r.getRelationName().toLowerCase())) {
+        e.execute(r.getCreateTableSql(dialect));
       }
-    } finally {
-      stmt.close();
     }
   }
 
-  private void addColumns(final RelationModel rel) throws SQLException {
+  private void addColumns(final StatementExecutor e, final RelationModel rel)
+      throws SQLException, OrmException {
     final SqlDialect dialect = dbDef.getDialect();
     final SchemaModel model = dbDef.getSchemaModel();
-    final Statement stmt = getConnection().createStatement();
-    try {
-      Set<String> have = dialect.listColumns( //
-          getConnection(), rel.getRelationName().toLowerCase());
-      for (final ColumnModel c : rel.getColumns()) {
-        if (!have.contains(c.getColumnName().toLowerCase())) {
-          dialect.addColumn(stmt, rel.getRelationName(), c);
-        }
+    Set<String> have = dialect.listColumns( //
+        getConnection(), rel.getRelationName().toLowerCase());
+    for (final ColumnModel c : rel.getColumns()) {
+      if (!have.contains(c.getColumnName().toLowerCase())) {
+        dialect.addColumn(e, rel.getRelationName(), c);
       }
-    } finally {
-      stmt.close();
     }
   }
 
-  public void renameField(String table, String from, String to)
-      throws OrmException {
+  public void renameField(final StatementExecutor e, String table, String from,
+      String to) throws OrmException {
     final RelationModel rel = findRelationModel(table);
     if (rel == null) {
       throw new OrmException("Relation " + table + " not defined");
@@ -146,17 +136,17 @@
     try {
       final Statement s = getConnection().createStatement();
       try {
-        getDialect().renameColumn(s, table, from, col);
+        getDialect().renameColumn(e, table, from, col);
       } finally {
         s.close();
       }
-    } catch (SQLException e) {
+    } catch (SQLException err) {
       throw new OrmException("Cannot rename " + table + "." + from + " to "
-          + to, e);
+          + to, err);
     }
   }
 
-  private RelationModel findRelationModel(String table) throws OrmException {
+  private RelationModel findRelationModel(String table) {
     for (final RelationModel rel : dbDef.getSchemaModel().getRelations()) {
       if (table.equalsIgnoreCase(rel.getRelationName())) {
         return rel;
@@ -165,74 +155,62 @@
     return null;
   }
 
-  public void pruneSchema() throws OrmException {
+  public void pruneSchema(final StatementExecutor e) throws OrmException {
     try {
-      pruneSequences();
-      pruneRelations();
+      pruneSequences(e);
+      pruneRelations(e);
 
       for (final RelationModel rel : dbDef.getSchemaModel().getRelations()) {
-        pruneColumns(rel);
+        pruneColumns(e, rel);
       }
-    } catch (SQLException e) {
-      throw new OrmException("Schema prune failure", e);
+    } catch (SQLException err) {
+      throw new OrmException("Schema prune failure", err);
     }
   }
 
-  private void pruneSequences() throws SQLException {
+  private void pruneSequences(final StatementExecutor e) throws SQLException,
+      OrmException {
     final SqlDialect dialect = dbDef.getDialect();
     final SchemaModel model = dbDef.getSchemaModel();
-    final Statement stmt = getConnection().createStatement();
-    try {
-      HashSet<String> want = new HashSet<String>();
-      for (final SequenceModel s : model.getSequences()) {
-        want.add(s.getSequenceName().toLowerCase());
+    HashSet<String> want = new HashSet<String>();
+    for (final SequenceModel s : model.getSequences()) {
+      want.add(s.getSequenceName().toLowerCase());
+    }
+    for (final String sequence : dialect.listSequences(getConnection())) {
+      if (!want.contains(sequence)) {
+        e.execute(dialect.getDropSequenceSql(sequence));
       }
-      for (final String sequence : dialect.listSequences(getConnection())) {
-        if (!want.contains(sequence)) {
-          stmt.execute(dialect.getDropSequenceSql(sequence));
-        }
-      }
-    } finally {
-      stmt.close();
     }
   }
 
-  private void pruneRelations() throws SQLException {
+  private void pruneRelations(final StatementExecutor e) throws SQLException,
+      OrmException {
     final SqlDialect dialect = dbDef.getDialect();
     final SchemaModel model = dbDef.getSchemaModel();
-    final Statement stmt = getConnection().createStatement();
-    try {
-      HashSet<String> want = new HashSet<String>();
-      for (final RelationModel r : model.getRelations()) {
-        want.add(r.getRelationName().toLowerCase());
+    HashSet<String> want = new HashSet<String>();
+    for (final RelationModel r : model.getRelations()) {
+      want.add(r.getRelationName().toLowerCase());
+    }
+    for (final String table : dialect.listTables(getConnection())) {
+      if (!want.contains(table)) {
+        e.execute("DROP TABLE " + table);
       }
-      for (final String table : dialect.listTables(getConnection())) {
-        if (!want.contains(table)) {
-          stmt.execute("DROP TABLE " + table);
-        }
-      }
-    } finally {
-      stmt.close();
     }
   }
 
-  private void pruneColumns(final RelationModel rel) throws SQLException {
+  private void pruneColumns(final StatementExecutor e, final RelationModel rel)
+      throws SQLException, OrmException {
     final SqlDialect dialect = dbDef.getDialect();
     final SchemaModel model = dbDef.getSchemaModel();
-    final Statement stmt = getConnection().createStatement();
-    try {
-      HashSet<String> want = new HashSet<String>();
-      for (final ColumnModel c : rel.getColumns()) {
-        want.add(c.getColumnName().toLowerCase());
+    HashSet<String> want = new HashSet<String>();
+    for (final ColumnModel c : rel.getColumns()) {
+      want.add(c.getColumnName().toLowerCase());
+    }
+    for (String column : dialect.listColumns( //
+        getConnection(), rel.getRelationName().toLowerCase())) {
+      if (!want.contains(column)) {
+        dialect.dropColumn(e, rel.getRelationName(), column);
       }
-      for (String column : dialect.listColumns( //
-          getConnection(), rel.getRelationName().toLowerCase())) {
-        if (!want.contains(column)) {
-          dialect.dropColumn(stmt, rel.getRelationName(), column);
-        }
-      }
-    } finally {
-      stmt.close();
     }
   }
 
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java b/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
index d2ec512..971118b 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
@@ -16,6 +16,7 @@
 
 import com.google.gwtorm.client.OrmDuplicateKeyException;
 import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.StatementExecutor;
 import com.google.gwtorm.schema.ColumnModel;
 
 import java.sql.Connection;
@@ -68,8 +69,8 @@
   }
 
   @Override
-  public void addColumn(Statement stmt, String tableName, ColumnModel col)
-      throws SQLException {
+  public void addColumn(StatementExecutor stmt, String tableName,
+      ColumnModel col) throws OrmException {
     final StringBuilder r = new StringBuilder();
     r.append("ALTER TABLE ");
     r.append(tableName);
@@ -93,8 +94,8 @@
   }
 
   @Override
-  public void renameColumn(Statement stmt, String tableName, String fromColumn,
-      ColumnModel col) throws SQLException {
+  public void renameColumn(StatementExecutor stmt, String tableName,
+      String fromColumn, ColumnModel col) throws OrmException {
     final StringBuilder r = new StringBuilder();
     r.append("ALTER TABLE ");
     r.append(tableName);
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java b/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
index 338c485..d3b77d6 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
@@ -16,6 +16,7 @@
 
 import com.google.gwtorm.client.Column;
 import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.StatementExecutor;
 import com.google.gwtorm.schema.ColumnModel;
 import com.google.gwtorm.schema.SequenceModel;
 
@@ -171,8 +172,8 @@
   }
 
   @Override
-  public void renameColumn(Statement stmt, String tableName, String fromColumn,
-      ColumnModel col) throws SQLException {
+  public void renameColumn(StatementExecutor stmt, String tableName,
+      String fromColumn, ColumnModel col) throws OrmException {
     StringBuffer r = new StringBuffer();
     r.append("ALTER TABLE ");
     r.append(tableName);
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java b/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
index 50cc2d3..e9b323d 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
@@ -16,6 +16,7 @@
 
 import com.google.gwtorm.client.OrmDuplicateKeyException;
 import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.StatementExecutor;
 import com.google.gwtorm.schema.ColumnModel;
 import com.google.gwtorm.schema.RelationModel;
 
@@ -72,8 +73,8 @@
   }
 
   @Override
-  public void renameColumn(Statement stmt, String tableName, String fromColumn,
-      ColumnModel col) throws SQLException {
+  public void renameColumn(StatementExecutor stmt, String tableName,
+      String fromColumn, ColumnModel col) throws OrmException {
     final StringBuilder r = new StringBuilder();
     r.append("ALTER TABLE ");
     r.append(tableName);
diff --git a/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java b/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
index c05992e..0e51db3 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
@@ -16,6 +16,7 @@
 
 import com.google.gwtorm.client.OrmException;
 import com.google.gwtorm.client.Sequence;
+import com.google.gwtorm.client.StatementExecutor;
 import com.google.gwtorm.schema.ColumnModel;
 import com.google.gwtorm.schema.RelationModel;
 import com.google.gwtorm.schema.SequenceModel;
@@ -244,13 +245,13 @@
   /**
    * Add one column to an existing table.
    *
-   * @param stmt statement to use to execute the SQL command(s).
+   * @param e statement to use to execute the SQL command(s).
    * @param tableName table to add the column onto.
    * @param col definition of the column.
-   * @throws SQLException the column could not be added.
+   * @throws OrmException the column could not be added.
    */
-  public void addColumn(Statement stmt, String tableName, ColumnModel col)
-      throws SQLException {
+  public void addColumn(StatementExecutor e, String tableName, ColumnModel col)
+      throws OrmException {
     final StringBuilder r = new StringBuilder();
     r.append("ALTER TABLE ");
     r.append(tableName);
@@ -263,38 +264,38 @@
       r.append(' ');
       r.append(check);
     }
-    stmt.execute(r.toString());
+    e.execute(r.toString());
   }
 
   /**
    * Drop one column from an existing table.
    *
-   * @param stmt statement to use to execute the SQL command(s).
+   * @param e statement to use to execute the SQL command(s).
    * @param tableName table to add the column onto.
    * @param column name of the column to drop.
-   * @throws SQLException the column could not be added.
+   * @throws OrmException the column could not be added.
    */
-  public void dropColumn(Statement stmt, String tableName, String column)
-      throws SQLException {
+  public void dropColumn(StatementExecutor e, String tableName, String column)
+      throws OrmException {
     final StringBuilder r = new StringBuilder();
     r.append("ALTER TABLE ");
     r.append(tableName);
     r.append(" DROP COLUMN ");
     r.append(column);
-    stmt.execute(r.toString());
+    e.execute(r.toString());
   }
 
   /**
    * Rename an existing column in a table.
    *
-   * @param stmt statement to use to execute the SQL command(s).
+   * @param e statement to use to execute the SQL command(s).
    * @param tableName table to add the column onto.
    * @param fromColumn source column name
    * @param col destination column definition
-   * @throws SQLException the column could not be renamed.
+   * @throws OrmException the column could not be renamed.
    */
-  public abstract void renameColumn(Statement stmt, String tableName,
-      String fromColumn, ColumnModel col) throws SQLException;
+  public abstract void renameColumn(StatementExecutor e, String tableName,
+      String fromColumn, ColumnModel col) throws OrmException;
 
   public abstract String getNextSequenceValueSql(String seqname);
 }
diff --git a/src/test/java/com/google/gwtorm/schema/sql/DialectH2Test.java b/src/test/java/com/google/gwtorm/schema/sql/DialectH2Test.java
index 5e1c552..38b08bb 100644
--- a/src/test/java/com/google/gwtorm/schema/sql/DialectH2Test.java
+++ b/src/test/java/com/google/gwtorm/schema/sql/DialectH2Test.java
@@ -20,6 +20,7 @@
 import com.google.gwtorm.data.TestAddress;
 import com.google.gwtorm.data.TestPerson;
 import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.jdbc.JdbcExecutor;
 import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.jdbc.SimpleDataSource;
 
@@ -28,13 +29,13 @@
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.sql.Statement;
 import java.util.Collections;
 import java.util.Properties;
 import java.util.Set;
 
 public class DialectH2Test extends TestCase {
   private Connection db;
+  private JdbcExecutor executor;
   private SqlDialect dialect;
   private Database<PhoneBookDb> phoneBook;
   private Database<PhoneBookDb2> phoneBook2;
@@ -44,6 +45,7 @@
     super.setUp();
     org.h2.Driver.load();
     db = DriverManager.getConnection("jdbc:h2:mem:DialectH2Test");
+    executor = new JdbcExecutor(db);
     dialect = new DialectH2().refine(db);
 
     final Properties p = new Properties();
@@ -57,6 +59,11 @@
 
   @Override
   protected void tearDown() {
+    if (executor != null) {
+      executor.close();
+    }
+    executor = null;
+
     if (db != null) {
       try {
         db.close();
@@ -67,16 +74,11 @@
     db = null;
   }
 
-  private void execute(final String sql) throws SQLException {
-    final Statement stmt = db.createStatement();
-    try {
-      stmt.execute(sql);
-    } finally {
-      stmt.close();
-    }
+  private void execute(final String sql) throws OrmException {
+    executor.execute(sql);
   }
 
-  public void testListSequences() throws SQLException {
+  public void testListSequences() throws OrmException, SQLException {
     assertTrue(dialect.listSequences(db).isEmpty());
 
     execute("CREATE SEQUENCE cnt");
@@ -88,7 +90,7 @@
     assertFalse(s.contains("foo"));
   }
 
-  public void testListTables() throws SQLException {
+  public void testListTables() throws OrmException,SQLException {
     assertTrue(dialect.listTables(db).isEmpty());
 
     execute("CREATE SEQUENCE cnt");
@@ -103,7 +105,7 @@
   public void testUpgradeSchema() throws SQLException, OrmException {
     final PhoneBookDb p = phoneBook.open();
     try {
-      p.updateSchema();
+      p.updateSchema(executor);
 
       execute("CREATE SEQUENCE cnt");
       execute("CREATE TABLE foo (cnt INT)");
@@ -115,7 +117,7 @@
 
       Set<String> sequences, tables;
 
-      p.updateSchema();
+      p.updateSchema(executor);
       sequences = dialect.listSequences(db);
       tables = dialect.listTables(db);
       assertTrue(sequences.contains("cnt"));
@@ -124,7 +126,7 @@
       assertTrue(sequences.contains("address_id"));
       assertTrue(tables.contains("addresses"));
 
-      p.pruneSchema();
+      p.pruneSchema(executor);
       sequences = dialect.listSequences(db);
       tables = dialect.listTables(db);
       assertFalse(sequences.contains("cnt"));
@@ -143,7 +145,8 @@
 
     final PhoneBookDb2 p2 = phoneBook2.open();
     try {
-      ((JdbcSchema)p2).renameField("people", "registered", "isRegistered");
+      ((JdbcSchema) p2).renameField(executor, "people", "registered",
+          "isRegistered");
     } finally {
       p2.close();
     }
diff --git a/src/test/java/com/google/gwtorm/schema/sql/DialectMySQLTest.java b/src/test/java/com/google/gwtorm/schema/sql/DialectMySQLTest.java
index f452455..7f96608 100644
--- a/src/test/java/com/google/gwtorm/schema/sql/DialectMySQLTest.java
+++ b/src/test/java/com/google/gwtorm/schema/sql/DialectMySQLTest.java
@@ -21,6 +21,7 @@
 import com.google.gwtorm.data.TestAddress;
 import com.google.gwtorm.data.TestPerson;
 import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.jdbc.JdbcExecutor;
 import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.jdbc.SimpleDataSource;
 
@@ -29,13 +30,13 @@
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.sql.Statement;
 import java.util.Collections;
 import java.util.Properties;
 import java.util.Set;
 
 public class DialectMySQLTest extends TestCase {
   private Connection db;
+  private JdbcExecutor executor;
   private SqlDialect dialect;
   private Database<PhoneBookDb> phoneBook;
   private Database<PhoneBookDb2> phoneBook2;
@@ -52,6 +53,7 @@
 
     final String url = "jdbc:mysql://" + host + "/" + database;
     db = DriverManager.getConnection(url, user, pass);
+    executor = new JdbcExecutor(db);
     dialect = new DialectMySQL().refine(db);
 
     final Properties p = new Properties();
@@ -62,7 +64,7 @@
     phoneBook =
         new Database<PhoneBookDb>(new SimpleDataSource(p), PhoneBookDb.class);
     phoneBook2 =
-      new Database<PhoneBookDb2>(new SimpleDataSource(p), PhoneBookDb2.class);
+        new Database<PhoneBookDb2>(new SimpleDataSource(p), PhoneBookDb2.class);
 
     drop("TABLE address_id");
     drop("TABLE addresses");
@@ -74,12 +76,17 @@
   private void drop(String drop) {
     try {
       execute("DROP " + drop);
-    } catch (SQLException e) {
+    } catch (OrmException e) {
     }
   }
 
   @Override
   protected void tearDown() {
+    if (executor != null) {
+      executor.close();
+    }
+    executor = null;
+
     if (db != null) {
       try {
         db.close();
@@ -90,16 +97,11 @@
     db = null;
   }
 
-  private void execute(final String sql) throws SQLException {
-    final Statement stmt = db.createStatement();
-    try {
-      stmt.execute(sql);
-    } finally {
-      stmt.close();
-    }
+  private void execute(final String sql) throws OrmException {
+    executor.execute(sql);
   }
 
-  public void testListSequences() throws SQLException {
+  public void testListSequences() throws OrmException, SQLException {
     assertTrue(dialect.listSequences(db).isEmpty());
 
     execute("CREATE TABLE cnt (s SERIAL)");
@@ -111,7 +113,7 @@
     assertFalse(s.contains("foo"));
   }
 
-  public void testListTables() throws SQLException {
+  public void testListTables() throws OrmException, SQLException {
     assertTrue(dialect.listTables(db).isEmpty());
 
     execute("CREATE TABLE cnt (s SERIAL)");
@@ -126,7 +128,7 @@
   public void testUpgradeSchema() throws SQLException, OrmException {
     final PhoneBookDb p = phoneBook.open();
     try {
-      p.updateSchema();
+      p.updateSchema(executor);
 
       execute("CREATE TABLE cnt (s SERIAL)");
       execute("CREATE TABLE foo (cnt INT)");
@@ -138,7 +140,7 @@
 
       Set<String> sequences, tables;
 
-      p.updateSchema();
+      p.updateSchema(executor);
       sequences = dialect.listSequences(db);
       tables = dialect.listTables(db);
       assertTrue(sequences.contains("cnt"));
@@ -147,7 +149,7 @@
       assertTrue(sequences.contains("address_id"));
       assertTrue(tables.contains("addresses"));
 
-      p.pruneSchema();
+      p.pruneSchema(executor);
       sequences = dialect.listSequences(db);
       tables = dialect.listTables(db);
       assertFalse(sequences.contains("cnt"));
@@ -166,7 +168,8 @@
 
     final PhoneBookDb2 p2 = phoneBook2.open();
     try {
-      ((JdbcSchema)p2).renameField("people", "registered", "isRegistered");
+      ((JdbcSchema) p2).renameField(executor, "people", "registered",
+          "isRegistered");
     } finally {
       p2.close();
     }
diff --git a/src/test/java/com/google/gwtorm/schema/sql/DialectPostgreSQLTest.java b/src/test/java/com/google/gwtorm/schema/sql/DialectPostgreSQLTest.java
index df43316..2e6550a 100644
--- a/src/test/java/com/google/gwtorm/schema/sql/DialectPostgreSQLTest.java
+++ b/src/test/java/com/google/gwtorm/schema/sql/DialectPostgreSQLTest.java
@@ -21,6 +21,7 @@
 import com.google.gwtorm.data.TestAddress;
 import com.google.gwtorm.data.TestPerson;
 import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.jdbc.JdbcExecutor;
 import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.jdbc.SimpleDataSource;
 
@@ -29,13 +30,13 @@
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.sql.Statement;
 import java.util.Collections;
 import java.util.Properties;
 import java.util.Set;
 
 public class DialectPostgreSQLTest extends TestCase {
   private Connection db;
+  private JdbcExecutor executor;
   private SqlDialect dialect;
   private Database<PhoneBookDb> phoneBook;
   private Database<PhoneBookDb2> phoneBook2;
@@ -50,6 +51,7 @@
     final String pass = "gwtorm";
 
     db = DriverManager.getConnection("jdbc:postgresql:" + database, user, pass);
+    executor = new JdbcExecutor(db);
     dialect = new DialectPostgreSQL().refine(db);
 
     final Properties p = new Properties();
@@ -60,7 +62,7 @@
     phoneBook =
         new Database<PhoneBookDb>(new SimpleDataSource(p), PhoneBookDb.class);
     phoneBook2 =
-      new Database<PhoneBookDb2>(new SimpleDataSource(p), PhoneBookDb2.class);
+        new Database<PhoneBookDb2>(new SimpleDataSource(p), PhoneBookDb2.class);
 
     drop("SEQUENCE address_id");
     drop("SEQUENCE cnt");
@@ -73,12 +75,17 @@
   private void drop(String drop) {
     try {
       execute("DROP " + drop);
-    } catch (SQLException e) {
+    } catch (OrmException e) {
     }
   }
 
   @Override
   protected void tearDown() {
+    if (executor != null) {
+      executor.close();
+    }
+    executor = null;
+
     if (db != null) {
       try {
         db.close();
@@ -89,16 +96,11 @@
     db = null;
   }
 
-  private void execute(final String sql) throws SQLException {
-    final Statement stmt = db.createStatement();
-    try {
-      stmt.execute(sql);
-    } finally {
-      stmt.close();
-    }
+  private void execute(final String sql) throws OrmException {
+    executor.execute(sql);
   }
 
-  public void testListSequences() throws SQLException {
+  public void testListSequences() throws OrmException, SQLException {
     assertTrue(dialect.listSequences(db).isEmpty());
 
     execute("CREATE SEQUENCE cnt");
@@ -110,7 +112,7 @@
     assertFalse(s.contains("foo"));
   }
 
-  public void testListTables() throws SQLException {
+  public void testListTables() throws OrmException, SQLException {
     assertTrue(dialect.listTables(db).isEmpty());
 
     execute("CREATE SEQUENCE cnt");
@@ -125,7 +127,7 @@
   public void testUpgradeSchema() throws SQLException, OrmException {
     final PhoneBookDb p = phoneBook.open();
     try {
-      p.updateSchema();
+      p.updateSchema(executor);
 
       execute("CREATE SEQUENCE cnt");
       execute("CREATE TABLE foo (cnt INT)");
@@ -137,7 +139,7 @@
 
       Set<String> sequences, tables;
 
-      p.updateSchema();
+      p.updateSchema(executor);
       sequences = dialect.listSequences(db);
       tables = dialect.listTables(db);
       assertTrue(sequences.contains("cnt"));
@@ -146,7 +148,7 @@
       assertTrue(sequences.contains("address_id"));
       assertTrue(tables.contains("addresses"));
 
-      p.pruneSchema();
+      p.pruneSchema(executor);
       sequences = dialect.listSequences(db);
       tables = dialect.listTables(db);
       assertFalse(sequences.contains("cnt"));
@@ -165,7 +167,8 @@
 
     final PhoneBookDb2 p2 = phoneBook2.open();
     try {
-      ((JdbcSchema)p2).renameField("people", "registered", "isRegistered");
+      ((JdbcSchema) p2).renameField(executor, "people", "registered",
+          "isRegistered");
     } finally {
       p2.close();
     }
diff --git a/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java b/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java
index da242bc..5e1ecac 100644
--- a/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java
+++ b/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java
@@ -21,6 +21,7 @@
 import com.google.gwtorm.data.PhoneBookDb;
 import com.google.gwtorm.data.TestPerson;
 import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.jdbc.JdbcExecutor;
 import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.jdbc.SimpleDataSource;
 
@@ -71,7 +72,12 @@
 
   protected PhoneBookDb openAndCreate() throws OrmException {
     final PhoneBookDb schema = open();
-    schema.updateSchema();
+    final JdbcExecutor e = new JdbcExecutor((JdbcSchema) schema);
+    try {
+      schema.updateSchema(e);
+    } finally {
+      e.close();
+    }
     return schema;
   }
 
@@ -104,7 +110,12 @@
 
   public void testCreateSchema() throws Exception {
     final PhoneBookDb schema = open();
-    schema.updateSchema();
+    final JdbcExecutor e = new JdbcExecutor((JdbcSchema) schema);
+    try {
+      schema.updateSchema(e);
+    } finally {
+      e.close();
+    }
   }
 
   public void testNextAddressId() throws Exception {