El Giganto Depot database dialects support from Zell. PostgresSQL here we

come. Automatic schema migration is currently disabled and remains to be
dialectified.


git-svn-id: https://samskivert.googlecode.com/svn/trunk@2136 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2007-07-25 20:53:40 +00:00
parent dd495a477a
commit 8a4c7ba54f
51 changed files with 3143 additions and 1796 deletions
@@ -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
@@ -48,11 +48,8 @@ import com.samskivert.jdbc.depot.annotation.TableGenerator;
import com.samskivert.jdbc.depot.annotation.Transient;
import com.samskivert.jdbc.depot.annotation.UniqueConstraint;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
import static com.samskivert.jdbc.depot.Log.log;
@@ -75,7 +72,6 @@ public class DepotMarshaller<T extends PersistentRecord>
_pclass = pclass;
Entity entity = pclass.getAnnotation(Entity.class);
Table table = null;
// see if this is a computed entity
Computed computed = pclass.getAnnotation(Computed.class);
@@ -89,11 +85,7 @@ public class DepotMarshaller<T extends PersistentRecord>
if (entity.name().length() > 0) {
_tableName = entity.name();
}
_postamble = entity.postamble();
}
// check for a Table annotation, for unique constraints
table = pclass.getAnnotation(Table.class);
}
// if the entity defines a new TableGenerator, map that in our static table as those are
@@ -127,8 +119,8 @@ public class DepotMarshaller<T extends PersistentRecord>
}
FieldMarshaller fm = FieldMarshaller.createMarshaller(field);
_fields.put(fm.getColumnName(), fm);
fields.add(fm.getColumnName());
_fields.put(field.getName(), fm);
fields.add(field.getName());
// check to see if this is our primary key
if (field.getAnnotation(Id.class) != null) {
@@ -181,7 +173,8 @@ public class DepotMarshaller<T extends PersistentRecord>
switch(gv.strategy()) {
case AUTO:
case IDENTITY:
_keyGenerator = new IdentityKeyGenerator();
_keyGenerator = new IdentityKeyGenerator(
gv, getTableName(), keyField.getColumnName());
break;
case TABLE:
@@ -191,7 +184,8 @@ public class DepotMarshaller<T extends PersistentRecord>
throw new IllegalArgumentException(
"Unknown generator [generator=" + name + "]");
}
_keyGenerator = new TableKeyGenerator(generator);
_keyGenerator = new TableKeyGenerator(
generator, gv, getTableName(), keyField.getColumnName());
break;
}
}
@@ -199,54 +193,6 @@ public class DepotMarshaller<T extends PersistentRecord>
// generate our full list of fields/columns for use in queries
_allFields = fields.toArray(new String[fields.size()]);
// if we're a computed entity, stop here
if (_tableName == null) {
return;
}
// figure out the list of fields that correspond to actual table columns and generate the
// SQL used to create and migrate our table (unless we're a computed entity)
_columnFields = new String[_allFields.length];
int jj = 0;
for (int ii = 0; ii < _allFields.length; ii++) {
// include all persistent non-computed fields
String colDef = _fields.get(_allFields[ii]).getColumnDefinition();
if (colDef != null) {
_columnFields[jj] = _allFields[ii];
_declarations.add(colDef);
jj ++;
}
}
_columnFields = ArrayUtil.splice(_columnFields, jj);
// determine whether we have any index definitions
if (entity != null) {
for (Index index : entity.indices()) {
// TODO: delegate this to a database specific SQL generator
_declarations.add(index.type() + " index " + index.name() +
" (" + StringUtil.join(index.columns(), ", ") + ")");
}
}
// add any unique constraints given
if (table != null) {
for (UniqueConstraint constraint : table.uniqueConstraints()) {
_declarations.add(
"UNIQUE (" + StringUtil.join(constraint.columnNames(), ", ") + ")");
}
}
// add the primary key, if we have one
if (hasPrimaryKey()) {
_declarations.add("PRIMARY KEY (" + getJoinedKeyColumns() + ")");
}
// if we did not find a schema version field, complain
if (_schemaVersion < 0) {
log.warning("Unable to read " + _pclass.getName() + "." + SCHEMA_VERSION_FIELD +
". Schema migration disabled.");
}
}
/**
@@ -265,7 +211,15 @@ public class DepotMarshaller<T extends PersistentRecord>
{
return _allFields;
}
/**
* Returns all the persistent fields that correspond to concrete table columns.
*/
public String[] getColumnFieldNames ()
{
return _columnFields;
}
/**
* Returns the {@link FieldMarshaller} for a named field on our persistent class.
*/
@@ -273,7 +227,7 @@ public class DepotMarshaller<T extends PersistentRecord>
{
return _fields.get(fieldName);
}
/**
* Returns true if our persistent object defines a primary key.
*/
@@ -283,14 +237,23 @@ public class DepotMarshaller<T extends PersistentRecord>
}
/**
* Return the names of the columns that constitute the primary key of our associated
* persistent record.
* Returns the {@link KeyGenerator} used to generate primary keys for this persistent object,
* or null if it does not use a key generator.
*/
public String[] getPrimaryKeyColumns ()
public KeyGenerator getKeyGenerator ()
{
return _keyGenerator;
}
/**
* Return the names of the columns that constitute the primary key of our associated persistent
* record.
*/
public String[] getPrimaryKeyFields ()
{
String[] pkcols = new String[_pkColumns.size()];
for (int ii = 0; ii < pkcols.length; ii ++) {
pkcols[ii] = _pkColumns.get(ii).getColumnName();
pkcols[ii] = _pkColumns.get(ii).getField().getName();
}
return pkcols;
}
@@ -359,11 +322,11 @@ public class DepotMarshaller<T extends PersistentRecord>
throw new UnsupportedOperationException(
getClass().getName() + " does not define a primary key");
}
String[] columns = new String[_pkColumns.size()];
String[] fields = new String[_pkColumns.size()];
for (int ii = 0; ii < _pkColumns.size(); ii++) {
columns[ii] = _pkColumns.get(ii).getColumnName();
fields[ii] = _pkColumns.get(ii).getField().getName();
}
return new Key<T>(_pclass, columns, values);
return new Key<T>(_pclass, fields, values);
}
/**
@@ -416,7 +379,7 @@ public class DepotMarshaller<T extends PersistentRecord>
// then create and populate the persistent object
T po = _pclass.newInstance();
for (FieldMarshaller fm : _fields.values()) {
if (!fields.contains(fm.getField().getName())) {
if (!fields.contains(fm.getColumnName())) {
// this field was not in the result set, make sure that's OK
if (fm.getComputed() != null && !fm.getComputed().required()) {
continue;
@@ -438,46 +401,6 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
/**
* Creates a statement that will insert the supplied persistent object into the database.
*/
public PreparedStatement createInsert (Connection conn, Object po)
throws SQLException
{
requireNotComputed("insert rows into");
try {
StringBuilder insert = new StringBuilder();
insert.append("insert into ").append(getTableName());
insert.append(" (").append(StringUtil.join(_columnFields, ","));
insert.append(")").append(" values(");
for (int ii = 0; ii < _columnFields.length; ii++) {
if (ii > 0) {
insert.append(", ");
}
insert.append("?");
}
insert.append(")");
// TODO: handle primary key, nullable fields specially?
PreparedStatement pstmt = conn.prepareStatement(insert.toString());
int idx = 0;
for (String field : _columnFields) {
_fields.get(field).readFromObject(po, pstmt, ++idx);
}
return pstmt;
} catch (SQLException sqe) {
// pass this on through
throw sqe;
} catch (Exception e) {
String errmsg = "Failed to marshall persistent object [pclass=" +
_pclass.getName() + "]";
throw (SQLException)new SQLException(errmsg).initCause(e);
}
}
/**
* Fills in the primary key just assigned to the supplied persistence object by the execution
* of the results of {@link #createInsert}.
@@ -485,7 +408,8 @@ public class DepotMarshaller<T extends PersistentRecord>
* @return the newly assigned primary key or null if the object does not use primary keys or
* this is not the right time to assign the key.
*/
public Key assignPrimaryKey (Connection conn, Object po, boolean postFactum)
public Key assignPrimaryKey (
Connection conn, DatabaseLiaison liaison, Object po, boolean postFactum)
throws SQLException
{
// if we have no primary key or no generator, then we're done
@@ -499,7 +423,7 @@ public class DepotMarshaller<T extends PersistentRecord>
}
try {
int nextValue = _keyGenerator.nextGeneratedValue(conn);
int nextValue = _keyGenerator.nextGeneratedValue(conn, liaison);
_pkColumns.get(0).getField().set(po, nextValue);
return makePrimaryKey(nextValue);
} catch (Exception e) {
@@ -508,131 +432,6 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
/**
* Creates a statement that will update the supplied persistent object using the supplied key.
*/
public PreparedStatement createUpdate (Connection conn, Object po, Where key)
throws SQLException
{
return createUpdate(conn, po, key, _columnFields);
}
/**
* Creates a statement that will update the supplied persistent object
* using the supplied key.
*/
public PreparedStatement createUpdate (
Connection conn, Object po, Where key, String[] modifiedFields)
throws SQLException
{
requireNotComputed("update rows in");
StringBuilder update = new StringBuilder();
update.append("update ").append(getTableName()).append(" set ");
int idx = 0;
for (String field : modifiedFields) {
if (idx++ > 0) {
update.append(", ");
}
update.append(field).append(" = ?");
}
key.appendClause(null, update);
try {
PreparedStatement pstmt = conn.prepareStatement(update.toString());
idx = 0;
// bind the update arguments
for (String field : modifiedFields) {
_fields.get(field).readFromObject(po, pstmt, ++idx);
}
// now bind the key arguments
key.bindClauseArguments(pstmt, ++idx);
return pstmt;
} catch (SQLException sqe) {
// pass this on through
throw sqe;
} catch (Exception e) {
String errmsg = "Failed to marshall persistent object " +
"[pclass=" + _pclass.getName() + "]";
throw (SQLException)new SQLException(errmsg).initCause(e);
}
}
/**
* Creates a statement that will update the specified set of fields for all persistent objects
* that match the supplied key.
*/
public PreparedStatement createPartialUpdate (
Connection conn, Where key, String[] modifiedFields, Object[] modifiedValues)
throws SQLException
{
requireNotComputed("update rows in");
StringBuilder update = new StringBuilder();
update.append("update ").append(getTableName()).append(" set ");
int idx = 0;
for (String field : modifiedFields) {
if (idx++ > 0) {
update.append(", ");
}
update.append(field).append(" = ?");
}
key.appendClause(null, update);
PreparedStatement pstmt = conn.prepareStatement(update.toString());
idx = 0;
// bind the update arguments
for (Object value : modifiedValues) {
// TODO: use the field marshaller?
pstmt.setObject(++idx, value);
}
// now bind the key arguments
key.bindClauseArguments(pstmt, ++idx);
return pstmt;
}
/**
* Creates a statement that will delete all rows matching the supplied key.
*/
public PreparedStatement createDelete (Connection conn, Where key)
throws SQLException
{
requireNotComputed("delete rows from");
StringBuilder query = new StringBuilder("delete from " + getTableName());
key.appendClause(null, query);
PreparedStatement pstmt = conn.prepareStatement(query.toString());
key.bindClauseArguments(pstmt, 1);
return pstmt;
}
/**
* Creates a statement that will update the specified set of fields, using the supplied literal
* SQL values, for all persistent objects that match the supplied key.
*/
public PreparedStatement createLiteralUpdate (
Connection conn, Where key, String[] modifiedFields, Object[] modifiedValues)
throws SQLException
{
requireNotComputed("update rows in");
StringBuilder update = new StringBuilder();
update.append("update ").append(getTableName()).append(" set ");
for (int ii = 0; ii < modifiedFields.length; ii++) {
if (ii > 0) {
update.append(", ");
}
update.append(modifiedFields[ii]).append(" = ");
update.append(modifiedValues[ii]);
}
key.appendClause(null, update);
PreparedStatement pstmt = conn.prepareStatement(update.toString());
key.bindClauseArguments(pstmt, 1);
return pstmt;
}
/**
* This is called by the persistence context to register a migration for the entity managed by
@@ -652,47 +451,122 @@ public class DepotMarshaller<T extends PersistentRecord>
protected void init (PersistenceContext ctx)
throws PersistenceException
{
SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.TRIVIAL);
if (_initialized) { // sanity check
throw new IllegalStateException(
"Cannot re-initialize marshaller [type=" + _pclass + "].");
}
_initialized = true;
// perform the context-sensitive initialization of the field marshallers
for (FieldMarshaller fm : _fields.values()) {
fm.init(builder);
}
// figure out the list of fields that correspond to actual table columns and generate the
// SQL used to create and migrate our table (unless we're a computed entity)
_columnFields = new String[_allFields.length];
String[] declarations = new String[_allFields.length];
int jj = 0;
for (int ii = 0; ii < _allFields.length; ii++) {
@SuppressWarnings("unchecked") FieldMarshaller<T> fm = _fields.get(_allFields[ii]);
// include all persistent non-computed fields
String colDef = fm.getColumnDefinition();
if (colDef != null) {
_columnFields[jj] = _allFields[ii];
declarations[jj] = colDef;
jj ++;
}
}
_columnFields = ArrayUtil.splice(_columnFields, jj);
declarations = ArrayUtil.splice(declarations, jj);
// if we have no table (i.e. we're a computed entity), we have nothing to create
if (getTableName() == null) {
return;
}
// add any additional unique constraints
String[][] uniqueConstraintColumns = null;
Table table = _pclass.getAnnotation(Table.class);
if (table != null) {
UniqueConstraint[] uCons = table.uniqueConstraints();
uniqueConstraintColumns = new String[uCons.length][];
for (int kk = 0; kk < uCons.length; kk ++) {
String[] columns = uCons[kk].columnNames();
for (int ii = 0; ii < columns.length; ii ++) {
FieldMarshaller fm = getFieldMarshaller(columns[ii]);
if (fm == null) {
throw new IllegalArgumentException(
"Unknown field in @UniqueConstraint: " + columns[ii]);
}
columns[ii] = fm.getColumnName();
}
uniqueConstraintColumns[kk] = columns;
}
}
// if we did not find a schema version field, complain
if (_schemaVersion < 0) {
log.warning("Unable to read " + _pclass.getName() + "." + SCHEMA_VERSION_FIELD +
". Schema migration disabled.");
}
// check to see if our schema version table exists, create it if not
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
JDBCUtil.createTableIfMissing(
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.createTableIfMissing(
conn, SCHEMA_VERSION_TABLE,
new String[] { "persistentClass VARCHAR(255) NOT NULL",
"version INTEGER NOT NULL" }, "");
new String[] { "persistentClass", "version" },
new String[] { "VARCHAR(255) NOT NULL", "INTEGER NOT NULL" },
null,
new String[] { "persistentClass" });
return 0;
}
});
// 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) throws SQLException {
if (!JDBCUtil.tableExists(conn, getTableName())) {
log.info("Creating table " + getTableName() + " (" + _declarations + ") " +
_postamble);
String[] definition = _declarations.toArray(new String[_declarations.size()]);
JDBCUtil.createTableIfMissing(conn, getTableName(), definition, _postamble);
updateVersion(conn, _schemaVersion);
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();
}
}
liaison.createTableIfMissing(conn, getTableName(), columns, fDeclarations,
fUniqueConstraintColumns, primaryKeyColumns);
updateVersion(conn, liaison, _schemaVersion);
return 0;
}
});
// 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 we have a key generator, initialize that too
if (_keyGenerator != null) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
_keyGenerator.init(conn);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
_keyGenerator.init(conn, liaison);
return 0;
}
});
@@ -706,9 +580,12 @@ public class DepotMarshaller<T extends PersistentRecord>
// make sure the versions match
int currentVersion = ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
String query = "select version from " + SCHEMA_VERSION_TABLE +
" where persistentClass = '" + getTableName() + "'";
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
String query =
" select " + liaison.columnSQL("version") +
" from " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" where " + liaison.columnSQL("persistentClass") +
" = '" + getTableName() + "'";
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery(query);
@@ -723,6 +600,12 @@ public class DepotMarshaller<T extends PersistentRecord>
return;
}
if (true) {
log.warning("Automatic schema migration is temporarily disabled.");
verifySchemasMatch(ctx);
return;
}
// otherwise try to migrate the schema
log.info("Migrating " + getTableName() + " from " +
currentVersion + " to " + _schemaVersion + "...");
@@ -736,35 +619,29 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
// enumerate all of the columns now that we've run our pre-migrations
Set<String> columns = new HashSet<String>();
Map<String, Set<String>> indexColumns = new HashMap<String, Set<String>>();
loadMetaData(ctx, columns, indexColumns);
// 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>(indexColumns.keySet());
Set<String> indicesCopy = new HashSet<String>(metaData.indexColumns.keySet());
// add any missing columns
for (String fname : _columnFields) {
FieldMarshaller fmarsh = _fields.get(fname);
if (columns.remove(fmarsh.getColumnName())) {
@SuppressWarnings("unchecked") final FieldMarshaller<T> fmarsh = _fields.get(fname);
if (metaData.tableColumns.remove(fmarsh.getColumnName())) {
continue;
}
// otherwise add the column
String coldef = fmarsh.getColumnDefinition();
String query = "alter table " + getTableName() + " add column " + coldef;
// try to add it to the appropriate spot
int fidx = ListUtil.indexOf(_allFields, fmarsh.getColumnName());
if (fidx == 0) {
query += " first";
} else {
query += " after " + _allFields[fidx-1];
}
log.info("Adding column to " + getTableName() + ": " + coldef);
ctx.invoke(new Modifier.Simple(query));
final String coldef = fmarsh.getColumnDefinition();
log.info("Adding column to " + getTableName() + ": " +
fmarsh.getColumnName() + " " + coldef);
ctx.invoke(new Modifier.Simple() {
protected String createQuery (DatabaseLiaison liaison) {
return "alter table " + liaison.tableSQL(getTableName()) +
" add column " + liaison.columnSQL(fmarsh.getColumnName()) + " " + coldef;
}
});
// if the column is a TIMESTAMP or DATETIME column, we need to run a special query to
// update all existing rows to the current time because MySQL annoyingly assigns
@@ -773,75 +650,84 @@ public class DepotMarshaller<T extends PersistentRecord>
// cannot accept CURRENT_TIME or NOW() defaults at all.
if (coldef.toLowerCase().indexOf(" timestamp") != -1 ||
coldef.toLowerCase().indexOf(" datetime") != -1) {
query = "update " + getTableName() + " set " + fmarsh.getColumnName() + " = NOW()";
log.info("Assigning current time to column: " + query);
ctx.invoke(new Modifier.Simple(query));
}
}
// 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
Entity entity = _pclass.getAnnotation(Entity.class);
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 " + index.name() +
" on " + 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 ++;
log.info("Assigning current time to " + fmarsh.getColumnName() + ".");
ctx.invoke(new Modifier.Simple() {
protected String createQuery (DatabaseLiaison liaison) {
// TODO: is NOW() standard SQL?
return "update " + liaison.tableSQL(getTableName()) +
" set " + liaison.columnSQL(fmarsh.getColumnName()) + " = NOW()";
}
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));
});
}
}
// 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() && !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));
// }
// }
// we do not auto-remove columns but rather require that EntityMigration.Drop records be
// registered by hand to avoid accidentally causin the loss of data
// registered by hand to avoid accidentally causing the loss of data
// we don't auto-remove indices either because we'd have to sort out the potentially
// complex origins of an index (which might be because of a @Unique column or maybe the
@@ -858,87 +744,96 @@ public class DepotMarshaller<T extends PersistentRecord>
// record our new version in the database
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
updateVersion(conn, _schemaVersion);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
updateVersion(conn, liaison, _schemaVersion);
return 0;
}
});
}
/**
* Loads up the metadata for our database table: the names of our columns and indices.
*/
protected void loadMetaData (PersistenceContext ctx, final Set<String> columns,
final Map<String, Set<String>> indexColumns)
protected TableMetaData loadMetaData (PersistenceContext ctx)
throws PersistenceException
{
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
DatabaseMetaData meta = conn.getMetaData();
ResultSet rs = meta.getColumns(null, null, getTableName(), "%");
while (rs.next()) {
columns.add(rs.getString("COLUMN_NAME"));
}
if (indexColumns != null) {
rs = meta.getIndexInfo(null, null, getTableName(), false, false);
while (rs.next()) {
String indexName = rs.getString("INDEX_NAME");
Set<String> set = indexColumns.get(indexName);
if (rs.getBoolean("NON_UNIQUE")) {
// not a unique index: just make sure there's an entry in the keyset
if (set == null) {
indexColumns.put(indexName, null);
}
} else {
// for unique indices we collect the column names
if (set == null) {
set = new HashSet<String>();
indexColumns.put(indexName, set);
}
set.add(rs.getString("COLUMN_NAME"));
}
}
}
return 0;
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 Set<String> tableColumns = new HashSet<String>();
public Map<String, Set<String>> indexColumns = new HashMap<String, Set<String>>();
public String pkName;
public Set<String> pkColumns = new HashSet<String>();
public TableMetaData (DatabaseMetaData meta)
throws SQLException
{
ResultSet rs = meta.getColumns(null, null, getTableName(), "%");
while (rs.next()) {
tableColumns.add(rs.getString("COLUMN_NAME"));
}
rs = meta.getIndexInfo(null, null, getTableName(), false, false);
while (rs.next()) {
String indexName = rs.getString("INDEX_NAME");
Set<String> set = indexColumns.get(indexName);
if (rs.getBoolean("NON_UNIQUE")) {
// not a unique index: just make sure there's an entry in the keyset
if (set == null) {
indexColumns.put(indexName, null);
}
} else {
// for unique indices we collect the column names
if (set == null) {
set = new HashSet<String>();
indexColumns.put(indexName, set);
}
set.add(rs.getString("COLUMN_NAME"));
}
}
rs = meta.getPrimaryKeys(null, null, getTableName());
while (rs.next()) {
pkName = rs.getString("PK_NAME");
pkColumns.add(rs.getString("COLUMN_NAME"));
}
}
}
/**
* Checks that there are no database columns for which we no longer have Java fields.
*/
protected void verifySchemasMatch (PersistenceContext ctx)
throws PersistenceException
{
Set<String> columns = new HashSet<String>();
loadMetaData(ctx, columns, null);
TableMetaData meta = loadMetaData(ctx);
for (String fname : _columnFields) {
FieldMarshaller fmarsh = _fields.get(fname);
columns.remove(fmarsh.getColumnName());
meta.tableColumns.remove(fmarsh.getColumnName());
}
for (String column : columns) {
for (String column : meta.tableColumns) {
log.warning(getTableName() + " contains stale column '" + column + "'.");
}
}
protected String getJoinedKeyColumns ()
{
return StringUtil.join(getPrimaryKeyColumns(), ", ");
}
protected void updateVersion (Connection conn, int version)
protected void updateVersion (Connection conn, DatabaseLiaison liaison, int version)
throws SQLException
{
String update = "update " + SCHEMA_VERSION_TABLE +
" set version = " + version + " where persistentClass = '" + getTableName() + "'";
String insert = "insert into " + SCHEMA_VERSION_TABLE +
" values('" + getTableName() + "', " + version + ")";
String update =
"update " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" set " + liaison.columnSQL("version") + " = " + version +
" where " + liaison.columnSQL("persistentClass") + " = '" + getTableName() + "'";
Statement stmt = conn.createStatement();
try {
if (stmt.executeUpdate(update) == 0) {
String insert =
"insert into " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" values('" + getTableName() + "', " + version + ")";
stmt.executeUpdate(insert);
}
} finally {
@@ -946,15 +841,6 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
protected void requireNotComputed (String action)
throws SQLException
{
if (getTableName() == null) {
throw new IllegalArgumentException(
"Can't " + action + " computed entities [class=" + _pclass + "]");
}
}
/** The persistent object class that we manage. */
protected Class<T> _pclass;
@@ -962,10 +848,10 @@ public class DepotMarshaller<T extends PersistentRecord>
protected String _tableName;
/** A field marshaller for each persistent field in our object. */
protected HashMap<String, FieldMarshaller> _fields = new HashMap<String, FieldMarshaller>();
protected Map<String, FieldMarshaller> _fields = new HashMap<String, FieldMarshaller>();
/** The field marshallers for our persistent object's primary key columns
* or null if it did not define a primary key. */
/** The field marshallers for our persistent object's primary key columns or null if it did not
* define a primary key. */
protected ArrayList<FieldMarshaller> _pkColumns;
/** The generator to use for auto-generating primary key values, or null. */
@@ -977,16 +863,9 @@ public class DepotMarshaller<T extends PersistentRecord>
/** The fields of our object with directly corresponding table columns. */
protected String[] _columnFields;
/** The version of our persistent object schema as specified in the class
* definition. */
/** The version of our persistent object schema as specified in the class definition. */
protected int _schemaVersion = -1;
/** Used when creating and migrating our table schema. */
protected ArrayList<String> _declarations = new ArrayList<String>();
/** Used when creating and migrating our table schema. */
protected String _postamble = "";
/** Indicates that we have been initialized (created or migrated our tables). */
protected boolean _initialized;