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.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; 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 // fetch all relevant information regarding our table from the database
final String[] fDeclarations = declarations; final TableMetaData metaData = ctx.invoke(new Query.TrivialQuery<TableMetaData>() {
final String[][] fUniqueConstraintColumns = uniqueConstraintColumns; public TableMetaData invoke (Connection conn, DatabaseLiaison dl) throws SQLException {
ctx.invoke(new Modifier() { return new TableMetaData(conn.getMetaData());
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;
} }
}); });
// ensure that all indices are created // if the table does not exist, create it
final Entity entity = _pclass.getAnnotation(Entity.class); if (!metaData.tableExists) {
if (entity != null && entity.indices().length > 0) { final Entity entity = _pclass.getAnnotation(Entity.class);
ctx.invoke(new Modifier() { final String[] fDeclarations = declarations;
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { final String[][] fUniqueConstraintColumns = uniqueConstraintColumns;
for (Index idx : entity.indices()) {
liaison.addIndexToTable(conn, getTableName(), idx.columns(), idx.name());
}
return entity.indices().length;
}
});
}
// if we have a key generator, initialize that too
if (_keyGenerator != null) {
ctx.invoke(new Modifier() { ctx.invoke(new Modifier() {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { 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; 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) { if (_schemaVersion < 0) {
verifySchemasMatch(ctx); // nope, versioning disabled
verifySchemasMatch(metaData, ctx);
return; return;
} }
@@ -625,20 +627,15 @@ public class DepotMarshaller<T extends PersistentRecord>
} }
} }
}); });
if (currentVersion == _schemaVersion) {
verifySchemasMatch(ctx);
return;
}
if (true) { if (currentVersion == _schemaVersion) {
log.warning("Automatic schema migration is temporarily disabled."); verifySchemasMatch(metaData, ctx);
verifySchemasMatch(ctx);
return; return;
} }
// otherwise try to migrate the schema // otherwise try to migrate the schema
log.info("Migrating " + getTableName() + " from " + log.info("Migrating " + getTableName() + " from " + currentVersion + " to " +
currentVersion + " to " + _schemaVersion + "..."); _schemaVersion + "...");
// run our pre-default-migrations // run our pre-default-migrations
for (EntityMigration migration : _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 // this is a little silly, but we need a copy for name disambiguation later
Set<String> indicesCopy = new HashSet<String>(metaData.indexColumns.keySet()); 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 // add or remove the primary key as needed
// is going to be PRIMARY seems like a MySQL-ism, and we'll do the rest of the index work if (hasPrimaryKey() && metaData.pkName == null) {
// when we do that. 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 } else if (!hasPrimaryKey() && metaData.pkName != null) {
// if (hasPrimaryKey() && !indexColumns.containsKey("PRIMARY")) { log.info("Dropping primary key.");
// String pkdef = "primary key (" + getJoinedKeyColumns() + ")"; ctx.invoke(new Modifier() {
// log.info("Adding primary key to " + getTableName() + ": " + pkdef); public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " add " + pkdef)); liaison.dropPrimaryKey(conn, getTableName(), metaData.pkName);
// return 0;
// } 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"));
// } Entity entity = _pclass.getAnnotation(Entity.class);
// // add any missing indices
// // add any missing indices for (final Index index : (entity == null ? new Index[0] : entity.indices())) {
// for (Index index : (entity == null ? new Index[0] : entity.indices())) { if (metaData.indexColumns.containsKey(index.name())) {
// if (indexColumns.containsKey(index.name())) { metaData.indexColumns.remove(index.name());
// indexColumns.remove(index.name()); continue;
// continue; }
// } ctx.invoke(new Modifier() {
// String indexdef = public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// "create " + index.type() + " index " + _liaison.indexSQL(index.name()) + liaison.addIndexToTable(
// " on " + _liaison.tableSQL(getTableName()) + " (" + StringUtil.join(index.columns(), ", ") + ")"; conn, getTableName(), index.columns(), index.name(), index.unique());
// log.info("Adding index: " + indexdef); return 0;
// 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()); // to get the @Table(uniqueIndices...) indices, we use our clever set of column name sets
// Table table; Set<Set<String>> uniqueIndices = new HashSet<Set<String>>(metaData.indexColumns.values());
// if (getTableName() != null && (table = _pclass.getAnnotation(Table.class)) != null) {
// Set<String> colSet = new HashSet<String>(); if (getTableName() != null && table != null) {
// for (UniqueConstraint constraint : table.uniqueConstraints()) { for (UniqueConstraint constraint : table.uniqueConstraints()) {
// // for each given UniqueConstraint, build a new column set // for each given UniqueConstraint, build a new column set
// colSet.clear(); Set<String> colSet = new HashSet<String>(Arrays.asList(constraint.fieldNames()));
// for (String column : constraint.columnNames()) {
// colSet.add(column); // and check if the table contained this set
// } if (uniqueIndices.contains(colSet)) {
// // and check if the table contained this set continue; // good, carry on
// 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!
// // else build the index; we'll use mysql's convention of naming it after a column, String indexName = colSet.iterator().next();
// // with possible _N disambiguation; luckily we made a copy of the index names! if (indicesCopy.contains(indexName)) {
// String indexName = colSet.iterator().next(); int num = 1;
// if (indicesCopy.contains(indexName)) { indexName += "_";
// int num = 1; while (indicesCopy.contains(indexName + num)) {
// indexName += "_"; num ++;
// while (indicesCopy.contains(indexName + num)) { }
// num ++; indexName += num;
// } }
// indexName += num;
// } final String[] colArr = colSet.toArray(new String[colSet.size()]);
// final String fName = indexName;
// String[] columnArr = colSet.toArray(new String[colSet.size()]); ctx.invoke(new Modifier() {
// String indexdef = "create unique index " + indexName + " on " + public int invoke (Connection conn, DatabaseLiaison dl) throws SQLException {
// getTableName() + " (" + StringUtil.join(columnArr, ", ") + ")"; dl.addIndexToTable(conn, getTableName(), colArr, fName, true);
// log.info("Adding unique index: " + indexdef); return 0;
// ctx.invoke(new Modifier.Simple(indexdef)); }
// } });
// } }
}
// we do not auto-remove columns but rather require that EntityMigration.Drop records be // 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 // 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 protected class TableMetaData
{ {
public boolean tableExists;
public Set<String> tableColumns = new HashSet<String>(); public Set<String> tableColumns = new HashSet<String>();
public Map<String, Set<String>> indexColumns = new HashMap<String, Set<String>>(); public Map<String, Set<String>> indexColumns = new HashMap<String, Set<String>>();
public String pkName; public String pkName;
@@ -802,6 +793,11 @@ public class DepotMarshaller<T extends PersistentRecord>
public TableMetaData (DatabaseMetaData meta) public TableMetaData (DatabaseMetaData meta)
throws SQLException throws SQLException
{ {
tableExists = meta.getTables("", "", getTableName(), null).next();
if (!tableExists) {
return;
}
ResultSet rs = meta.getColumns(null, null, getTableName(), "%"); ResultSet rs = meta.getColumns(null, null, getTableName(), "%");
while (rs.next()) { while (rs.next()) {
tableColumns.add(rs.getString("COLUMN_NAME")); 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. * 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 throws PersistenceException
{ {
TableMetaData meta = loadMetaData(ctx);
for (String fname : _columnFields) { for (String fname : _columnFields) {
FieldMarshaller fmarsh = _fields.get(fname); FieldMarshaller fmarsh = _fields.get(fname);
meta.tableColumns.remove(fmarsh.getColumnName()); meta.tableColumns.remove(fmarsh.getColumnName());
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell // Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
// //
// 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
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell // Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
// //
// 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
@@ -46,13 +46,13 @@ public interface Query<T>
public void updateCache (PersistenceContext ctx, T result) { public void updateCache (PersistenceContext ctx, T result) {
} }
} }
/** /**
* Any query may elect to utilize the built-in cache by returning a non-null {@link CacheKey} * 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 * 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 * single entities by primary key, but even entire collections can be cached under a single
* key. * key.
* *
* Great care must be taken to invalidate such cached collections when their constituent * Great care must be taken to invalidate such cached collections when their constituent
* entities are invalidated. This is generally done using {@link CacheListener} and * entities are invalidated. This is generally done using {@link CacheListener} and
* {@link CacheInvalidator}. * {@link CacheInvalidator}.
@@ -60,7 +60,7 @@ public interface Query<T>
public CacheKey getCacheKey (); 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) public T invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException; throws SQLException;
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell // Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
// //
// 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
@@ -34,9 +34,8 @@ public @interface Index
/** Defines the name of the index. */ /** Defines the name of the index. */
String name (); String name ();
/** Configures the type of this index if necessary. MySQL supports FULLTEXT, SPATIAL and /** Does this index enforce a uniqueness constraint? */
* UNIQUE. */ boolean unique () default false;
String type () default "";
/** Defines the columns on which the index operates. */ /** Defines the columns on which the index operates. */
String[] columns () default {}; String[] columns () default {};