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:
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user