From d45da29c7bf375332a4dac40b6e7b3ed32acef57 Mon Sep 17 00:00:00 2001 From: samskivert Date: Wed, 10 Sep 2008 02:00:19 +0000 Subject: [PATCH] Created an explicit EntityMigration.Add for when you want to add a column to a database and need to provide a defaultValue at the time that you add it, but don't want to have to define a permanent default value on the field. git-svn-id: https://samskivert.googlecode.com/svn/trunk@2409 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- src/java/com/samskivert/jdbc/BaseLiaison.java | 15 ++++++ .../com/samskivert/jdbc/DatabaseLiaison.java | 14 ++++-- .../jdbc/depot/EntityMigration.java | 46 +++++++++++++++++++ .../jdbc/depot/tests/TestRecord.java | 15 ++++-- .../jdbc/depot/tests/TestRepository.java | 13 ++++-- 5 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/java/com/samskivert/jdbc/BaseLiaison.java b/src/java/com/samskivert/jdbc/BaseLiaison.java index b9190232..40d38d2b 100644 --- a/src/java/com/samskivert/jdbc/BaseLiaison.java +++ b/src/java/com/samskivert/jdbc/BaseLiaison.java @@ -160,6 +160,21 @@ public abstract class BaseLiaison implements DatabaseLiaison return true; } + // from DatabaseLiaison + public boolean addColumn (Connection conn, String table, String column, + ColumnDefinition newColumnDef, boolean check) + throws SQLException + { + if (check && tableContainsColumn(conn, table, column)) { + return false; + } + + executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " ADD COLUMN " + + columnSQL(column) + " " + expandDefinition(newColumnDef)); + log.info("Database column '" + column + "' added to table '" + table + "'."); + return true; + } + // from DatabaseLiaison public boolean changeColumn (Connection conn, String table, String column, String type, Boolean nullable, Boolean unique, String defaultValue) diff --git a/src/java/com/samskivert/jdbc/DatabaseLiaison.java b/src/java/com/samskivert/jdbc/DatabaseLiaison.java index 01cf2c48..f5bd835e 100644 --- a/src/java/com/samskivert/jdbc/DatabaseLiaison.java +++ b/src/java/com/samskivert/jdbc/DatabaseLiaison.java @@ -114,11 +114,19 @@ public interface DatabaseLiaison Connection conn, String table, String column, String definition, boolean check) throws SQLException; + /** + * Adds a column to a table with the given definition. Tests for the previous existence of + * the column iff 'check' is true. + */ + public boolean addColumn ( + Connection conn, String table, String column, ColumnDefinition columnDef, boolean check) + 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. Any of the type, - * nullable, unique and defaultValue arguments may be null, in which case the implementation - * will attempt to leave that aspect of the column unchanged. + * false if the database did or did not report a schema modification. Any of the nullable, + * unique and defaultValue arguments may be null, in which case the implementation will attempt + * to leave that aspect of the column unchanged. */ public boolean changeColumn ( Connection conn, String table, String column, String type, Boolean nullable, diff --git a/src/java/com/samskivert/jdbc/depot/EntityMigration.java b/src/java/com/samskivert/jdbc/depot/EntityMigration.java index 41fc15c2..b802f022 100644 --- a/src/java/com/samskivert/jdbc/depot/EntityMigration.java +++ b/src/java/com/samskivert/jdbc/depot/EntityMigration.java @@ -150,6 +150,52 @@ public abstract class EntityMigration extends Modifier protected ColumnDefinition _newColumnDef; } + /** + * A convenient migration for adding a new column that requires a default value to be specified + * during the addition. Normally Depot will automatically handle column addition, but if you + * have a column that normally does not have a default value but needs one when it is added to + * a table with existing rows, you can use this migration. + * + * @see Column#defaultValue + */ + public static class Add extends EntityMigration + { + public Add (int targetVersion, String fieldName, String defaultValue) { + super(targetVersion); + _fieldName = fieldName; + _defaultValue = defaultValue; + } + + @Override public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + // override the default value in the column definition with the one provided + ColumnDefinition defColumnDef = new ColumnDefinition( + _newColumnDef.getType(), _newColumnDef.isNullable(), + _newColumnDef.isUnique(), _defaultValue); + // first add the column with the overridden default value + if (liaison.addColumn(conn, _tableName, _fieldName, defColumnDef, true)) { + // then change the column to the permanent default value + liaison.changeColumn(conn, _tableName, _fieldName, _newColumnDef.getType(), + null, null, _newColumnDef.getDefaultValue()); + return 1; + } + return 0; + } + + @Override public boolean runBeforeDefault () { + return true; + } + + @Override + protected void init (String tableName, Map> marshallers) { + super.init(tableName, marshallers); + _columnName = marshallers.get(_fieldName).getColumnName(); + _newColumnDef = marshallers.get(_fieldName).getColumnDefinition(); + } + + protected String _fieldName, _columnName, _defaultValue; + protected ColumnDefinition _newColumnDef; + } + /** * If this method returns true, this migration will be run before the default * migrations, if false it will be run after. diff --git a/src/java/com/samskivert/jdbc/depot/tests/TestRecord.java b/src/java/com/samskivert/jdbc/depot/tests/TestRecord.java index 30711677..673843bc 100644 --- a/src/java/com/samskivert/jdbc/depot/tests/TestRecord.java +++ b/src/java/com/samskivert/jdbc/depot/tests/TestRecord.java @@ -61,6 +61,13 @@ public class TestRecord extends PersistentRecord public static final ColumnExp AGE_C = new ColumnExp(TestRecord.class, AGE); + /** The column identifier for the {@link #homeTown} field. */ + public static final String HOME_TOWN = "homeTown"; + + /** The qualified column identifier for the {@link #homeTown} field. */ + public static final ColumnExp HOME_TOWN_C = + new ColumnExp(TestRecord.class, HOME_TOWN); + /** The column identifier for the {@link #created} field. */ public static final String CREATED = "created"; @@ -76,21 +83,19 @@ public class TestRecord extends PersistentRecord new ColumnExp(TestRecord.class, LAST_MODIFIED); // AUTO-GENERATED: FIELDS END - public static final int SCHEMA_VERSION = 1; + public static final int SCHEMA_VERSION = 2; @Id public int recordId; - @Column(nullable=false) public String name; - @Column(nullable=false) public int age; - @Column(nullable=false) + public String homeTown; + public Date created; - @Column(nullable=false) public Timestamp lastModified; @Override diff --git a/src/java/com/samskivert/jdbc/depot/tests/TestRepository.java b/src/java/com/samskivert/jdbc/depot/tests/TestRepository.java index cdc9902f..38f828dd 100644 --- a/src/java/com/samskivert/jdbc/depot/tests/TestRepository.java +++ b/src/java/com/samskivert/jdbc/depot/tests/TestRepository.java @@ -24,11 +24,10 @@ import java.sql.Date; import java.sql.Timestamp; import java.util.Set; -// import com.samskivert.jdbc.ConnectionProvider; import com.samskivert.jdbc.StaticConnectionProvider; import com.samskivert.util.RandomUtil; import com.samskivert.jdbc.depot.DepotRepository; -// import com.samskivert.jdbc.depot.EntityMigration; +import com.samskivert.jdbc.depot.EntityMigration; import com.samskivert.jdbc.depot.PersistenceContext; import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.expression.LiteralExp; @@ -46,8 +45,12 @@ public class TestRepository extends DepotRepository PersistenceContext perCtx = new PersistenceContext( "test", new StaticConnectionProvider("depot.properties")); -// tests a bogus rename migration -// perCtx.registerMigration(TestRecord.class, new EntityMigration.Rename(1, "foo", "bar")); + // tests a bogus rename migration + // perCtx.registerMigration(TestRecord.class, new EntityMigration.Rename(1, "foo", "bar")); + + // tests a custom add column migration + perCtx.registerMigration(TestRecord.class, + new EntityMigration.Add(2, TestRecord.HOME_TOWN, "'Anytown USA'")); TestRepository repo = new TestRepository(perCtx); @@ -60,6 +63,7 @@ public class TestRepository extends DepotRepository record.name = "Elvis"; record.age = 99; record.created = now; + record.homeTown = "Right here"; record.lastModified = tnow; repo.insert(record); @@ -75,6 +79,7 @@ public class TestRepository extends DepotRepository record.recordId = ii; record.name = "Spam!"; record.age = RandomUtil.getInt(150); + record.homeTown = "Over there"; record.created = now; record.lastModified = tnow; repo.insert(record);