diff --git a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java index 74cc70d..fbe858d 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java @@ -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 } }); - // 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() { + 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 } } }); - 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 } } - // 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 indicesCopy = new HashSet(metaData.indexColumns.keySet()); @@ -691,70 +685,77 @@ public class DepotMarshaller } } - // 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> uniqueIndices = new HashSet>(indexColumns.values()); -// Table table; -// if (getTableName() != null && (table = _pclass.getAnnotation(Table.class)) != null) { -// Set colSet = new HashSet(); -// 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> uniqueIndices = new HashSet>(metaData.indexColumns.values()); + + if (getTableName() != null && table != null) { + for (UniqueConstraint constraint : table.uniqueConstraints()) { + // for each given UniqueConstraint, build a new column set + Set colSet = new HashSet(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 }); } - protected TableMetaData loadMetaData (PersistenceContext ctx) - throws PersistenceException - { - return ctx.invoke(new Query.TrivialQuery() { - public TableMetaData invoke (Connection conn, DatabaseLiaison liaison) - throws SQLException { - return new TableMetaData(conn.getMetaData()); - } - }); - } - protected class TableMetaData { + public boolean tableExists; public Set tableColumns = new HashSet(); public Map> indexColumns = new HashMap>(); public String pkName; @@ -802,6 +793,11 @@ public class DepotMarshaller 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 /** * 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()); diff --git a/src/java/com/samskivert/jdbc/depot/FindOneQuery.java b/src/java/com/samskivert/jdbc/depot/FindOneQuery.java index e83fa6c..90d6485 100644 --- a/src/java/com/samskivert/jdbc/depot/FindOneQuery.java +++ b/src/java/com/samskivert/jdbc/depot/FindOneQuery.java @@ -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 diff --git a/src/java/com/samskivert/jdbc/depot/Query.java b/src/java/com/samskivert/jdbc/depot/Query.java index e54b8a0..4a912d9 100644 --- a/src/java/com/samskivert/jdbc/depot/Query.java +++ b/src/java/com/samskivert/jdbc/depot/Query.java @@ -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 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 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; diff --git a/src/java/com/samskivert/jdbc/depot/annotation/Index.java b/src/java/com/samskivert/jdbc/depot/annotation/Index.java index 304a3a9..9a7b520 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/Index.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/Index.java @@ -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 {};