Reinstate automatic schema migration, per Zell.

This commit is contained in:
Michael Bayne
2007-08-06 21:52:52 +00:00
parent 8eb5d24be9
commit eb9c7c9c74
4 changed files with 133 additions and 139 deletions
@@ -31,6 +31,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -551,60 +552,61 @@ public class DepotMarshaller<T extends PersistentRecord>
}
});
// now create the table for our persistent class if it does not exist
final String[] fDeclarations = declarations;
final String[][] fUniqueConstraintColumns = uniqueConstraintColumns;
ctx.invoke(new Modifier() {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
String[] columns = new String[_columnFields.length];
for (int ii = 0; ii < columns.length; ii ++) {
columns[ii] = _fields.get(_columnFields[ii]).getColumnName();
}
String[] primaryKeyColumns = null;
if (_pkColumns != null) {
primaryKeyColumns = new String[_pkColumns.size()];
for (int ii = 0; ii < primaryKeyColumns.length; ii ++) {
primaryKeyColumns[ii] = _pkColumns.get(ii).getColumnName();
}
}
if (liaison.createTableIfMissing(conn, getTableName(), columns, fDeclarations,
fUniqueConstraintColumns, primaryKeyColumns)) {
for (FullTextIndex fti : _fullTextIndexes.values()) {
builder.addFullTextSearch(conn, DepotMarshaller.this, fti);
}
}
updateVersion(conn, liaison, _schemaVersion);
return 0;
// fetch all relevant information regarding our table from the database
final TableMetaData metaData = ctx.invoke(new Query.TrivialQuery<TableMetaData>() {
public TableMetaData invoke (Connection conn, DatabaseLiaison dl) throws SQLException {
return new TableMetaData(conn.getMetaData());
}
});
// ensure that all indices are created
final Entity entity = _pclass.getAnnotation(Entity.class);
if (entity != null && entity.indices().length > 0) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
for (Index idx : entity.indices()) {
liaison.addIndexToTable(conn, getTableName(), idx.columns(), idx.name());
}
return entity.indices().length;
}
});
}
// if the table does not exist, create it
if (!metaData.tableExists) {
final Entity entity = _pclass.getAnnotation(Entity.class);
final String[] fDeclarations = declarations;
final String[][] fUniqueConstraintColumns = uniqueConstraintColumns;
// if we have a key generator, initialize that too
if (_keyGenerator != null) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
_keyGenerator.init(conn, liaison);
// create the table
String[] columns = new String[_columnFields.length];
for (int ii = 0; ii < columns.length; ii ++) {
columns[ii] = _fields.get(_columnFields[ii]).getColumnName();
}
String[] primaryKeyColumns = null;
if (_pkColumns != null) {
primaryKeyColumns = new String[_pkColumns.size()];
for (int ii = 0; ii < primaryKeyColumns.length; ii ++) {
primaryKeyColumns[ii] = _pkColumns.get(ii).getColumnName();
}
}
liaison.createTableIfMissing(conn, getTableName(), columns, fDeclarations,
fUniqueConstraintColumns, primaryKeyColumns);
// add its indexen
for (Index idx : entity.indices()) {
liaison.addIndexToTable(
conn, getTableName(), idx.columns(), idx.name(), idx.unique());
}
if (_keyGenerator != null) {
_keyGenerator.init(conn, liaison);
}
for (FullTextIndex fti : _fullTextIndexes.values()) {
builder.addFullTextSearch(conn, DepotMarshaller.this, fti);
}
updateVersion(conn, liaison, _schemaVersion);
return 0;
}
});
// and we're done
return;
}
// if schema versioning is disabled, stop now
// if the table exists, see if should attempt automatic schema migration
if (_schemaVersion < 0) {
verifySchemasMatch(ctx);
// nope, versioning disabled
verifySchemasMatch(metaData, ctx);
return;
}
@@ -625,20 +627,15 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
});
if (currentVersion == _schemaVersion) {
verifySchemasMatch(ctx);
return;
}
if (true) {
log.warning("Automatic schema migration is temporarily disabled.");
verifySchemasMatch(ctx);
if (currentVersion == _schemaVersion) {
verifySchemasMatch(metaData, ctx);
return;
}
// otherwise try to migrate the schema
log.info("Migrating " + getTableName() + " from " +
currentVersion + " to " + _schemaVersion + "...");
log.info("Migrating " + getTableName() + " from " + currentVersion + " to " +
_schemaVersion + "...");
// run our pre-default-migrations
for (EntityMigration migration : _migrations) {
@@ -649,9 +646,6 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
// enumerate all columns and indexes now that we've run our pre-migrations
TableMetaData metaData = loadMetaData(ctx);
// this is a little silly, but we need a copy for name disambiguation later
Set<String> indicesCopy = new HashSet<String>(metaData.indexColumns.keySet());
@@ -691,70 +685,77 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
// TODO: This needs more work to be database-independent, I think. Assuming the index name
// is going to be PRIMARY seems like a MySQL-ism, and we'll do the rest of the index work
// when we do that.
// add or remove the primary key as needed
if (hasPrimaryKey() && metaData.pkName == null) {
log.info("Adding primary key.");
ctx.invoke(new Modifier() {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.addPrimaryKey(conn, getTableName(), getPrimaryKeyFields());
return 0;
}
});
// // add or remove the primary key as needed
// if (hasPrimaryKey() && !indexColumns.containsKey("PRIMARY")) {
// String pkdef = "primary key (" + getJoinedKeyColumns() + ")";
// log.info("Adding primary key to " + getTableName() + ": " + pkdef);
// ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " add " + pkdef));
//
// } else if (!hasPrimaryKey() && indexColumns.containsKey("PRIMARY")) {
// indexColumns.remove("PRIMARY");
// log.info("Dropping primary from " + getTableName());
// ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " drop primary key"));
// }
//
// // add any missing indices
// for (Index index : (entity == null ? new Index[0] : entity.indices())) {
// if (indexColumns.containsKey(index.name())) {
// indexColumns.remove(index.name());
// continue;
// }
// String indexdef =
// "create " + index.type() + " index " + _liaison.indexSQL(index.name()) +
// " on " + _liaison.tableSQL(getTableName()) + " (" + StringUtil.join(index.columns(), ", ") + ")";
// log.info("Adding index: " + indexdef);
// ctx.invoke(new Modifier.Simple(indexdef));
// }
//
// // to get the @Table(uniqueIndices...) indices, we use our clever set of column name sets
// Set<Set<String>> uniqueIndices = new HashSet<Set<String>>(indexColumns.values());
// Table table;
// if (getTableName() != null && (table = _pclass.getAnnotation(Table.class)) != null) {
// Set<String> colSet = new HashSet<String>();
// for (UniqueConstraint constraint : table.uniqueConstraints()) {
// // for each given UniqueConstraint, build a new column set
// colSet.clear();
// for (String column : constraint.columnNames()) {
// colSet.add(column);
// }
// // and check if the table contained this set
// if (uniqueIndices.contains(colSet)) {
// continue; // good, carry on
// }
//
// // else build the index; we'll use mysql's convention of naming it after a column,
// // with possible _N disambiguation; luckily we made a copy of the index names!
// String indexName = colSet.iterator().next();
// if (indicesCopy.contains(indexName)) {
// int num = 1;
// indexName += "_";
// while (indicesCopy.contains(indexName + num)) {
// num ++;
// }
// indexName += num;
// }
//
// String[] columnArr = colSet.toArray(new String[colSet.size()]);
// String indexdef = "create unique index " + indexName + " on " +
// getTableName() + " (" + StringUtil.join(columnArr, ", ") + ")";
// log.info("Adding unique index: " + indexdef);
// ctx.invoke(new Modifier.Simple(indexdef));
// }
// }
} else if (!hasPrimaryKey() && metaData.pkName != null) {
log.info("Dropping primary key.");
ctx.invoke(new Modifier() {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.dropPrimaryKey(conn, getTableName(), metaData.pkName);
return 0;
}
});
}
Entity entity = _pclass.getAnnotation(Entity.class);
// add any missing indices
for (final Index index : (entity == null ? new Index[0] : entity.indices())) {
if (metaData.indexColumns.containsKey(index.name())) {
metaData.indexColumns.remove(index.name());
continue;
}
ctx.invoke(new Modifier() {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.addIndexToTable(
conn, getTableName(), index.columns(), index.name(), index.unique());
return 0;
}
});
}
// to get the @Table(uniqueIndices...) indices, we use our clever set of column name sets
Set<Set<String>> uniqueIndices = new HashSet<Set<String>>(metaData.indexColumns.values());
if (getTableName() != null && table != null) {
for (UniqueConstraint constraint : table.uniqueConstraints()) {
// for each given UniqueConstraint, build a new column set
Set<String> colSet = new HashSet<String>(Arrays.asList(constraint.fieldNames()));
// and check if the table contained this set
if (uniqueIndices.contains(colSet)) {
continue; // good, carry on
}
// else build the index; we'll use mysql's convention of naming it after a column,
// with possible _N disambiguation; luckily we made a copy of the index names!
String indexName = colSet.iterator().next();
if (indicesCopy.contains(indexName)) {
int num = 1;
indexName += "_";
while (indicesCopy.contains(indexName + num)) {
num ++;
}
indexName += num;
}
final String[] colArr = colSet.toArray(new String[colSet.size()]);
final String fName = indexName;
ctx.invoke(new Modifier() {
public int invoke (Connection conn, DatabaseLiaison dl) throws SQLException {
dl.addIndexToTable(conn, getTableName(), colArr, fName, true);
return 0;
}
});
}
}
// we do not auto-remove columns but rather require that EntityMigration.Drop records be
// registered by hand to avoid accidentally causing the loss of data
@@ -781,19 +782,9 @@ public class DepotMarshaller<T extends PersistentRecord>
});
}
protected TableMetaData loadMetaData (PersistenceContext ctx)
throws PersistenceException
{
return ctx.invoke(new Query.TrivialQuery<TableMetaData>() {
public TableMetaData invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
return new TableMetaData(conn.getMetaData());
}
});
}
protected class TableMetaData
{
public boolean tableExists;
public Set<String> tableColumns = new HashSet<String>();
public Map<String, Set<String>> indexColumns = new HashMap<String, Set<String>>();
public String pkName;
@@ -802,6 +793,11 @@ public class DepotMarshaller<T extends PersistentRecord>
public TableMetaData (DatabaseMetaData meta)
throws SQLException
{
tableExists = meta.getTables("", "", getTableName(), null).next();
if (!tableExists) {
return;
}
ResultSet rs = meta.getColumns(null, null, getTableName(), "%");
while (rs.next()) {
tableColumns.add(rs.getString("COLUMN_NAME"));
@@ -838,10 +834,9 @@ public class DepotMarshaller<T extends PersistentRecord>
/**
* Checks that there are no database columns for which we no longer have Java fields.
*/
protected void verifySchemasMatch (PersistenceContext ctx)
protected void verifySchemasMatch (TableMetaData meta, PersistenceContext ctx)
throws PersistenceException
{
TableMetaData meta = loadMetaData(ctx);
for (String fname : _columnFields) {
FieldMarshaller fmarsh = _fields.get(fname);
meta.tableColumns.remove(fmarsh.getColumnName());
@@ -3,7 +3,7 @@
//
// 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
@@ -3,7 +3,7 @@
//
// 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
@@ -46,13 +46,13 @@ public interface Query<T>
public void updateCache (PersistenceContext ctx, T result) {
}
}
/**
* Any query may elect to utilize the built-in cache by returning a non-null {@link CacheKey}
* in this method. This is done automatically by the {@link DepotRepository} when looking up
* single entities by primary key, but even entire collections can be cached under a single
* key.
*
*
* Great care must be taken to invalidate such cached collections when their constituent
* entities are invalidated. This is generally done using {@link CacheListener} and
* {@link CacheInvalidator}.
@@ -60,7 +60,7 @@ public interface Query<T>
public CacheKey getCacheKey ();
/**
* Performs the actual JDBC operations associated with this query.
* Performs the actual JDBC operations associated with this query.
*/
public T invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException;
@@ -3,7 +3,7 @@
//
// 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
@@ -34,9 +34,8 @@ public @interface Index
/** Defines the name of the index. */
String name ();
/** Configures the type of this index if necessary. MySQL supports FULLTEXT, SPATIAL and
* UNIQUE. */
String type () default "";
/** Does this index enforce a uniqueness constraint? */
boolean unique () default false;
/** Defines the columns on which the index operates. */
String[] columns () default {};