diff --git a/src/java/com/samskivert/jdbc/BaseLiaison.java b/src/java/com/samskivert/jdbc/BaseLiaison.java index 5ceb4b18..2ddfb055 100644 --- a/src/java/com/samskivert/jdbc/BaseLiaison.java +++ b/src/java/com/samskivert/jdbc/BaseLiaison.java @@ -160,19 +160,24 @@ public abstract class BaseLiaison implements DatabaseLiaison } // from DatabaseLiaison - public boolean changeColumn (Connection conn, String table, String column, String definition) + public boolean changeColumn (Connection conn, String table, String column, String type, + Boolean nullable, Boolean unique, String defaultValue) throws SQLException { + String defStr = expandDefinition( + type, nullable != null ? nullable : false, unique != null ? unique : false, + defaultValue); + executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " CHANGE " + - columnSQL(column) + " " + columnSQL(column) + " " + definition); + columnSQL(column) + " " + columnSQL(column) + " " + defStr); Log.info("Database column '" + column + "' of table '" + table + "' modified to have " + - "definition '" + definition + "'."); + "definition '" + defStr + "'."); return true; } // from DatabaseLiaison public boolean renameColumn (Connection conn, String table, String from, String to, - String newColumnDef) + ColumnDefinition newColumnDef) throws SQLException { executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " RENAME COLUMN " + @@ -194,7 +199,7 @@ public abstract class BaseLiaison implements DatabaseLiaison // from DatabaseLiaison public boolean createTableIfMissing ( - Connection conn, String table, String[] columns, String[] definitions, + Connection conn, String table, String[] columns, ColumnDefinition[] definitions, String[][] uniqueConstraintColumns, String[] primaryKeyColumns) throws SQLException { @@ -211,7 +216,8 @@ public abstract class BaseLiaison implements DatabaseLiaison if (ii > 0) { builder.append(", "); } - builder.append(columnSQL(columns[ii])).append(" ").append(definitions[ii]); + builder.append(columnSQL(columns[ii])).append(" "); + builder.append(expandDefinition(definitions[ii])); } if (uniqueConstraintColumns != null && uniqueConstraintColumns.length > 0) { @@ -245,6 +251,17 @@ public abstract class BaseLiaison implements DatabaseLiaison return true; } + /** + * Create an SQL string that summarizes a column definition in that format generally + * accepted in table creation and column addition statements, e.g. + * INTEGER UNIQUE NOT NULL DEFAULT 100 + */ + public String expandDefinition (ColumnDefinition def) + { + return expandDefinition( + def.getType(), def.isNullable(), def.isUnique(), def.getDefaultValue()); + } + protected int executeQuery (Connection conn, String query) throws SQLException { @@ -255,4 +272,24 @@ public abstract class BaseLiaison implements DatabaseLiaison JDBCUtil.close(stmt); } } + + protected String expandDefinition ( + String type, boolean nullable, boolean unique, String defaultValue) + { + StringBuilder builder = new StringBuilder(type); + + if (!nullable) { + builder.append(" NOT NULL"); + } + if (unique) { + builder.append(" UNIQUE"); + } + + // append the default value if one was specified + if (defaultValue != null) { + builder.append(" DEFAULT ").append(defaultValue); + } + + return builder.toString(); + } } diff --git a/src/java/com/samskivert/jdbc/ColumnDefinition.java b/src/java/com/samskivert/jdbc/ColumnDefinition.java new file mode 100644 index 00000000..2bde1c1d --- /dev/null +++ b/src/java/com/samskivert/jdbc/ColumnDefinition.java @@ -0,0 +1,74 @@ +// +// $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; + +/** + * An object representing the configuration of a typical SQL column. + */ +public class ColumnDefinition +{ + public ColumnDefinition (String type) + { + this(type, false, false, null); + } + + public ColumnDefinition ( + String type, boolean nullable, boolean unique, String defaultValue) + { + if (type == null) { + throw new IllegalArgumentException("cannot build column definition without type"); + } + + _type = type; + _nullable = nullable; + _unique = unique; + _defaultValue = defaultValue; + } + + /** Returns the SQL type of this column, e.g. INTEGER. */ + public String getType () + { + return _type; + } + + /** Determines whether or not this column should allow NULL values. */ + public boolean isNullable () + { + return _nullable; + } + + /** Determines whether or not this column should reject duplicate values. */ + public boolean isUnique () + { + return _unique; + } + + /** Returns the value to insert into this column instead of nulls, if any. */ + public String getDefaultValue () + { + return _defaultValue; + } + + protected String _type; + protected boolean _nullable; + protected boolean _unique; + protected String _defaultValue; +} diff --git a/src/java/com/samskivert/jdbc/DatabaseLiaison.java b/src/java/com/samskivert/jdbc/DatabaseLiaison.java index 1e232859..0ff7c683 100644 --- a/src/java/com/samskivert/jdbc/DatabaseLiaison.java +++ b/src/java/com/samskivert/jdbc/DatabaseLiaison.java @@ -65,6 +65,13 @@ public interface DatabaseLiaison Connection conn, String table, String column, int first, int step) throws SQLException; + /** + * This method deletes the column value auto-generator described in {@link + * #lastInsertedId}. + */ + public void deleteGenerator (Connection conn, String tableName, String columnName) + 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 @@ -117,23 +124,25 @@ public interface DatabaseLiaison /** * 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. + * 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. */ public boolean changeColumn ( - Connection conn, String table, String column, String definition) - throws SQLException; + Connection conn, String table, String column, String type, Boolean nullable, + Boolean unique, String defaultValue) + 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. * - * @param newColumnDef the full definition of the new column, including its new name (MySQL + * @param columnDef the full definition of the new column, including its new name (MySQL * requires this for a column rename). */ public boolean renameColumn ( - Connection conn, String table, String oldColumn, String newColumn, String newColumnDef) + Connection conn, String table, String oldColumn, String newColumn, + ColumnDefinition columnDef) throws SQLException; /** @@ -142,10 +151,17 @@ public interface DatabaseLiaison * Returns true if the table was successfully created, false if it already existed. */ public boolean createTableIfMissing ( - Connection conn, String table, String[] columns, String[] definitions, + Connection conn, String table, String[] columns, ColumnDefinition[] declarations, String[][] uniqueConstraintColumns, String[] primaryKeyColumns) throws SQLException; + /** + * Create an SQL string that summarizes a column definition in that format generally + * accepted in table creation and column addition statements, e.g. + * INTEGER UNIQUE NOT NULL DEFAULT 100 + */ + public String expandDefinition (ColumnDefinition coldef); + /** * 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. diff --git a/src/java/com/samskivert/jdbc/DefaultLiaison.java b/src/java/com/samskivert/jdbc/DefaultLiaison.java index 0e764ebb..84fcd96d 100644 --- a/src/java/com/samskivert/jdbc/DefaultLiaison.java +++ b/src/java/com/samskivert/jdbc/DefaultLiaison.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 @@ -47,6 +47,13 @@ public class DefaultLiaison extends BaseLiaison return false; } + // from DatabaseLiaison + public void deleteGenerator (Connection conn, String tableName, String columnName) + throws SQLException + { + // nothing doing + } + // from DatabaseLiaison public void initializeGenerator ( Connection conn, String table, String column, int value, int size) diff --git a/src/java/com/samskivert/jdbc/MySQLLiaison.java b/src/java/com/samskivert/jdbc/MySQLLiaison.java index c686373a..55ae62ab 100644 --- a/src/java/com/samskivert/jdbc/MySQLLiaison.java +++ b/src/java/com/samskivert/jdbc/MySQLLiaison.java @@ -59,6 +59,13 @@ public class MySQLLiaison extends BaseLiaison // AUTO_INCREMENT does not allow this kind of control } + public void deleteGenerator (Connection conn, String tableName, String columnName) + throws SQLException + { + // AUTO_INCREMENT does not create any database entities that we need to delete + } + + // from DatabaseLiaison public int lastInsertedId (Connection conn, String table, String column) throws SQLException { @@ -120,14 +127,14 @@ public class MySQLLiaison extends BaseLiaison @Override // from BaseLiaison public boolean renameColumn (Connection conn, String table, String oldColumnName, - String newColumnName, String newColumnDef) + String newColumnName, ColumnDefinition newColumnDef) throws SQLException { if (!tableContainsColumn(conn, table, oldColumnName)) { return false; } executeQuery(conn, "ALTER TABLE " + table + " CHANGE " + oldColumnName + " " + - newColumnName + " " + newColumnDef); + newColumnName + " " + expandDefinition(newColumnDef)); Log.info("Renamed column '" + oldColumnName + "' on table '" + table + "' to '" + newColumnName + "'"); return true; diff --git a/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java b/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java index 69fdbab0..dad7e177 100644 --- a/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java +++ b/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java @@ -21,8 +21,6 @@ package com.samskivert.jdbc; import java.sql.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import com.samskivert.Log; @@ -85,23 +83,46 @@ public class PostgreSQLLiaison extends BaseLiaison " restart with " + first + " increment " + step); } - @Override // from DatabaseLiaison - public boolean changeColumn (Connection conn, String table, String column, String definition) + // from DatabaseLiaison + public void deleteGenerator (Connection conn, String table, String column) throws SQLException { - // we need to handle nullability separately; TODO: make this less of a hack - boolean notNull = false; - Matcher match = NOT_NULL.matcher(definition); - if (match.find()) { - definition = match.replaceFirst(""); - notNull = true; + executeQuery(conn, "drop sequence if exists \"" + table + "_" + column + "_seq\""); + } + + @Override // from DatabaseLiaison + public boolean changeColumn (Connection conn, String table, String column, String type, + Boolean nullable, Boolean unique, String defaultValue) + throws SQLException + { + StringBuilder log = new StringBuilder(); + if (type != null) { + executeQuery( + conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + columnSQL(column) + + " TYPE " + type); + log.append("type=" + type); + } + if (nullable != null) { + executeQuery( + conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + columnSQL(column) + + " " + (nullable.booleanValue() ? "SET NOT NULL" : "DROP NOT NULL")); + if (log.length() > 0) log.append(", "); + log.append("nullable=" + nullable); + } + if (unique != null) { + // TODO: I think this requires ALTER TABLE DROP CONSTRAINT and so on + if (log.length() > 0) log.append(", "); + log.append("unique=" + unique + " (not implemented yet)"); + } + if (defaultValue != null) { + executeQuery( + conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + columnSQL(column) + + " " + (defaultValue.length() > 0 ? "SET DEFAULT " + defaultValue : "DROP DEFAULT")); + if (log.length() > 0) log.append(", "); + log.append("defaultValue=" + defaultValue); } - executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + - columnSQL(column) + " TYPE " + definition); - executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + - columnSQL(column) + " " + (notNull ? "SET NOT NULL" : "DROP NOT NULL")); Log.info("Database column '" + column + "' of table '" + table + "' modified to have " + - "definition '" + definition + "'."); + "definition [" + log + "]."); return true; } @@ -122,7 +143,4 @@ public class PostgreSQLLiaison extends BaseLiaison { return "\"" + index + "\""; } - - protected static final Pattern NOT_NULL = - Pattern.compile(" NOT NULL", Pattern.CASE_INSENSITIVE); } diff --git a/src/java/com/samskivert/jdbc/TransitionRepository.java b/src/java/com/samskivert/jdbc/TransitionRepository.java index 67f3fa6a..7158d256 100644 --- a/src/java/com/samskivert/jdbc/TransitionRepository.java +++ b/src/java/com/samskivert/jdbc/TransitionRepository.java @@ -203,7 +203,11 @@ public class TransitionRepository extends SimpleRepository conn, "TRANSITIONS", new String[] { "CLASS", "NAME", "APPLIED" }, - new String[] { "VARCHAR(200)", "VARCHAR(50)", "TIMESTAMP NOT NULL" }, + new ColumnDefinition[] { + new ColumnDefinition("VARCHAR(200)", true, false, null), + new ColumnDefinition("VARCHAR(50)", true, false, null), + new ColumnDefinition("TIMESTAMP") + }, null, new String[] { "CLASS", "NAME" }); }