Don't pass around and store column definitions as SQL strings, but introduce an object to contain that data. PostgreSQL's extensions does column alteration in individual tweaks, where MySQL just accepts a raw string and figures out what to do.

git-svn-id: https://samskivert.googlecode.com/svn/trunk@2233 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
zell
2007-10-11 18:12:37 +00:00
parent 05b6eb6fd9
commit 1e50256ca2
7 changed files with 199 additions and 36 deletions
+43 -6
View File
@@ -160,19 +160,24 @@ public abstract class BaseLiaison implements DatabaseLiaison
} }
// from 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 throws SQLException
{ {
String defStr = expandDefinition(
type, nullable != null ? nullable : false, unique != null ? unique : false,
defaultValue);
executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " CHANGE " + 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 " + Log.info("Database column '" + column + "' of table '" + table + "' modified to have " +
"definition '" + definition + "'."); "definition '" + defStr + "'.");
return true; return true;
} }
// from DatabaseLiaison // from DatabaseLiaison
public boolean renameColumn (Connection conn, String table, String from, String to, public boolean renameColumn (Connection conn, String table, String from, String to,
String newColumnDef) ColumnDefinition newColumnDef)
throws SQLException throws SQLException
{ {
executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " RENAME COLUMN " + executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " RENAME COLUMN " +
@@ -194,7 +199,7 @@ public abstract class BaseLiaison implements DatabaseLiaison
// from DatabaseLiaison // from DatabaseLiaison
public boolean createTableIfMissing ( public boolean createTableIfMissing (
Connection conn, String table, String[] columns, String[] definitions, Connection conn, String table, String[] columns, ColumnDefinition[] definitions,
String[][] uniqueConstraintColumns, String[] primaryKeyColumns) String[][] uniqueConstraintColumns, String[] primaryKeyColumns)
throws SQLException throws SQLException
{ {
@@ -211,7 +216,8 @@ public abstract class BaseLiaison implements DatabaseLiaison
if (ii > 0) { if (ii > 0) {
builder.append(", "); 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) { if (uniqueConstraintColumns != null && uniqueConstraintColumns.length > 0) {
@@ -245,6 +251,17 @@ public abstract class BaseLiaison implements DatabaseLiaison
return true; 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) protected int executeQuery (Connection conn, String query)
throws SQLException throws SQLException
{ {
@@ -255,4 +272,24 @@ public abstract class BaseLiaison implements DatabaseLiaison
JDBCUtil.close(stmt); 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();
}
} }
@@ -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;
}
@@ -65,6 +65,13 @@ public interface DatabaseLiaison
Connection conn, String table, String column, int first, int step) Connection conn, String table, String column, int first, int step)
throws SQLException; 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 * 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 * 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 * 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. * 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
* TODO: We may wish to further split column definitions into type, nullability, etc. * will attempt to leave that aspect of the column unchanged.
*/ */
public boolean changeColumn ( public boolean changeColumn (
Connection conn, String table, String column, String definition) Connection conn, String table, String column, String type, Boolean nullable,
throws SQLException; Boolean unique, String defaultValue)
throws SQLException;
/** /**
* Alter the name, but not the definition, of a given column on a given table. Returns true or * 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. * 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). * requires this for a column rename).
*/ */
public boolean renameColumn ( 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; throws SQLException;
/** /**
@@ -142,10 +151,17 @@ public interface DatabaseLiaison
* Returns true if the table was successfully created, false if it already existed. * Returns true if the table was successfully created, false if it already existed.
*/ */
public boolean createTableIfMissing ( public boolean createTableIfMissing (
Connection conn, String table, String[] columns, String[] definitions, Connection conn, String table, String[] columns, ColumnDefinition[] declarations,
String[][] uniqueConstraintColumns, String[] primaryKeyColumns) String[][] uniqueConstraintColumns, String[] primaryKeyColumns)
throws SQLException; 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; * Returns true if the specified table exists and contains an index of the specified name;
* false if either conditions does not hold true. <em>Note:</em> the names are case sensitive. * false if either conditions does not hold true. <em>Note:</em> the names are case sensitive.
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2001-2007 Michael Bayne // Copyright (C) 2001-2007 Michael Bayne
// //
// This library is free software; you can redistribute it and/or modify it // 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 // 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 // by the Free Software Foundation; either version 2.1 of the License, or
@@ -47,6 +47,13 @@ public class DefaultLiaison extends BaseLiaison
return false; return false;
} }
// from DatabaseLiaison
public void deleteGenerator (Connection conn, String tableName, String columnName)
throws SQLException
{
// nothing doing
}
// from DatabaseLiaison // from DatabaseLiaison
public void initializeGenerator ( public void initializeGenerator (
Connection conn, String table, String column, int value, int size) Connection conn, String table, String column, int value, int size)
@@ -59,6 +59,13 @@ public class MySQLLiaison extends BaseLiaison
// AUTO_INCREMENT does not allow this kind of control // 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 // from DatabaseLiaison
public int lastInsertedId (Connection conn, String table, String column) throws SQLException public int lastInsertedId (Connection conn, String table, String column) throws SQLException
{ {
@@ -120,14 +127,14 @@ public class MySQLLiaison extends BaseLiaison
@Override // from BaseLiaison @Override // from BaseLiaison
public boolean renameColumn (Connection conn, String table, String oldColumnName, public boolean renameColumn (Connection conn, String table, String oldColumnName,
String newColumnName, String newColumnDef) String newColumnName, ColumnDefinition newColumnDef)
throws SQLException throws SQLException
{ {
if (!tableContainsColumn(conn, table, oldColumnName)) { if (!tableContainsColumn(conn, table, oldColumnName)) {
return false; return false;
} }
executeQuery(conn, "ALTER TABLE " + table + " CHANGE " + oldColumnName + " " + executeQuery(conn, "ALTER TABLE " + table + " CHANGE " + oldColumnName + " " +
newColumnName + " " + newColumnDef); newColumnName + " " + expandDefinition(newColumnDef));
Log.info("Renamed column '" + oldColumnName + "' on table '" + table + "' to '" + Log.info("Renamed column '" + oldColumnName + "' on table '" + table + "' to '" +
newColumnName + "'"); newColumnName + "'");
return true; return true;
@@ -21,8 +21,6 @@
package com.samskivert.jdbc; package com.samskivert.jdbc;
import java.sql.*; import java.sql.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.samskivert.Log; import com.samskivert.Log;
@@ -85,23 +83,46 @@ public class PostgreSQLLiaison extends BaseLiaison
" restart with " + first + " increment " + step); " restart with " + first + " increment " + step);
} }
@Override // from DatabaseLiaison // from DatabaseLiaison
public boolean changeColumn (Connection conn, String table, String column, String definition) public void deleteGenerator (Connection conn, String table, String column)
throws SQLException throws SQLException
{ {
// we need to handle nullability separately; TODO: make this less of a hack executeQuery(conn, "drop sequence if exists \"" + table + "_" + column + "_seq\"");
boolean notNull = false; }
Matcher match = NOT_NULL.matcher(definition);
if (match.find()) { @Override // from DatabaseLiaison
definition = match.replaceFirst(""); public boolean changeColumn (Connection conn, String table, String column, String type,
notNull = true; 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 " + Log.info("Database column '" + column + "' of table '" + table + "' modified to have " +
"definition '" + definition + "'."); "definition [" + log + "].");
return true; return true;
} }
@@ -122,7 +143,4 @@ public class PostgreSQLLiaison extends BaseLiaison
{ {
return "\"" + index + "\""; return "\"" + index + "\"";
} }
protected static final Pattern NOT_NULL =
Pattern.compile(" NOT NULL", Pattern.CASE_INSENSITIVE);
} }
@@ -203,7 +203,11 @@ public class TransitionRepository extends SimpleRepository
conn, conn,
"TRANSITIONS", "TRANSITIONS",
new String[] { "CLASS", "NAME", "APPLIED" }, 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, null,
new String[] { "CLASS", "NAME" }); new String[] { "CLASS", "NAME" });
} }