blob: 9efd29ccc89c7192e4f7280c34c813f02f9f7640 [file] [log] [blame]
/*
* Copyright (c) 2007-2013, 2015 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
* Stefan Winkler - Bug 259402
* Stefan Winkler - Bug 271444: [DB] Multiple refactorings bug 271444
* Stefan Winkler - Bug 249610: [DB] Support external references (Implementation)
* Stefan Winkler - Bug 289056: [DB] Exception "ERROR: relation "cdo_external_refs" does not exist" while executing test-suite for PostgreSQL
*/
package org.eclipse.emf.cdo.server.internal.db;
import org.eclipse.emf.cdo.common.CDOCommonRepository.IDGenerationLocation;
import org.eclipse.emf.cdo.common.branch.CDOBranch;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.branch.CDOBranchVersion;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.revision.CDOAllRevisionsProvider;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
import org.eclipse.emf.cdo.server.ISession;
import org.eclipse.emf.cdo.server.ITransaction;
import org.eclipse.emf.cdo.server.IView;
import org.eclipse.emf.cdo.server.StoreThreadLocal;
import org.eclipse.emf.cdo.server.db.IDBStore;
import org.eclipse.emf.cdo.server.db.IIDHandler;
import org.eclipse.emf.cdo.server.db.IMetaDataManager;
import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
import org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.IMappingConstants;
import org.eclipse.emf.cdo.server.internal.db.messages.Messages;
import org.eclipse.emf.cdo.spi.server.InternalRepository;
import org.eclipse.emf.cdo.spi.server.InternalSession;
import org.eclipse.emf.cdo.spi.server.LongIDStoreAccessor;
import org.eclipse.emf.cdo.spi.server.Store;
import org.eclipse.emf.cdo.spi.server.StoreAccessorPool;
import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBUtil;
import org.eclipse.net4j.db.IDBAdapter;
import org.eclipse.net4j.db.IDBConnection;
import org.eclipse.net4j.db.IDBConnectionProvider;
import org.eclipse.net4j.db.IDBDatabase;
import org.eclipse.net4j.db.IDBPreparedStatement;
import org.eclipse.net4j.db.IDBPreparedStatement.ReuseProbability;
import org.eclipse.net4j.db.IDBSchemaTransaction;
import org.eclipse.net4j.db.ddl.IDBField;
import org.eclipse.net4j.db.ddl.IDBSchema;
import org.eclipse.net4j.db.ddl.IDBTable;
import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.om.monitor.ProgressDistributor;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
/**
* @author Eike Stepper
*/
public class DBStore extends Store implements IDBStore, IMappingConstants, CDOAllRevisionsProvider
{
public static final String TYPE = "db"; //$NON-NLS-1$
public static final int SCHEMA_VERSION = 4;
private static final int FIRST_START = -1;
private static final String PROP_SCHEMA_VERSION = "org.eclipse.emf.cdo.server.db.schemaVersion"; //$NON-NLS-1$
private static final String PROP_REPOSITORY_CREATED = "org.eclipse.emf.cdo.server.db.repositoryCreated"; //$NON-NLS-1$
private static final String PROP_REPOSITORY_STOPPED = "org.eclipse.emf.cdo.server.db.repositoryStopped"; //$NON-NLS-1$
private static final String PROP_NEXT_LOCAL_CDOID = "org.eclipse.emf.cdo.server.db.nextLocalCDOID"; //$NON-NLS-1$
private static final String PROP_LAST_CDOID = "org.eclipse.emf.cdo.server.db.lastCDOID"; //$NON-NLS-1$
private static final String PROP_LAST_BRANCHID = "org.eclipse.emf.cdo.server.db.lastBranchID"; //$NON-NLS-1$
private static final String PROP_LAST_LOCAL_BRANCHID = "org.eclipse.emf.cdo.server.db.lastLocalBranchID"; //$NON-NLS-1$
private static final String PROP_LAST_COMMITTIME = "org.eclipse.emf.cdo.server.db.lastCommitTime"; //$NON-NLS-1$
private static final String PROP_LAST_NONLOCAL_COMMITTIME = "org.eclipse.emf.cdo.server.db.lastNonLocalCommitTime"; //$NON-NLS-1$
private static final String PROP_GRACEFULLY_SHUT_DOWN = "org.eclipse.emf.cdo.server.db.gracefullyShutDown"; //$NON-NLS-1$
private long creationTime;
private boolean firstTime;
private Map<String, String> properties;
private int idColumnLength = IDBField.DEFAULT;
private IIDHandler idHandler;
private IMetaDataManager metaDataManager = new MetaDataManager(this);
private DurableLockingManager durableLockingManager = new DurableLockingManager(this);
private IMappingStrategy mappingStrategy;
private IDBDatabase database;
private IDBAdapter dbAdapter;
private IDBConnectionProvider dbConnectionProvider;
@ExcludeFromDump
private transient ProgressDistributor accessorWriteDistributor = new ProgressDistributor.Geometric()
{
@Override
public String toString()
{
String result = "accessorWriteDistributor"; //$NON-NLS-1$
if (getRepository() != null)
{
result += ": " + getRepository().getName(); //$NON-NLS-1$
}
return result;
}
};
@ExcludeFromDump
private transient StoreAccessorPool readerPool = new StoreAccessorPool(this, null);
@ExcludeFromDump
private transient StoreAccessorPool writerPool = new StoreAccessorPool(this, null);
@ExcludeFromDump
private transient Timer connectionKeepAliveTimer;
public DBStore()
{
super(TYPE, null, set(ChangeFormat.REVISION, ChangeFormat.DELTA), //
set(RevisionTemporality.AUDITING, RevisionTemporality.NONE), //
set(RevisionParallelism.NONE, RevisionParallelism.BRANCHING));
}
public IMappingStrategy getMappingStrategy()
{
return mappingStrategy;
}
public void setMappingStrategy(IMappingStrategy mappingStrategy)
{
this.mappingStrategy = mappingStrategy;
mappingStrategy.setStore(this);
}
public IDBAdapter getDBAdapter()
{
return dbAdapter;
}
public void setDBAdapter(IDBAdapter dbAdapter)
{
this.dbAdapter = dbAdapter;
}
public void setProperties(Map<String, String> properties)
{
checkInactive();
this.properties = properties;
}
public Map<String, String> getProperties()
{
return properties;
}
public int getIDColumnLength()
{
return idColumnLength;
}
public IIDHandler getIDHandler()
{
return idHandler;
}
public IDBDatabase getDatabase()
{
return database;
}
public Connection getConnection()
{
Connection connection = dbConnectionProvider.getConnection();
if (connection == null)
{
throw new DBException("No connection from connection provider: " + dbConnectionProvider); //$NON-NLS-1$
}
try
{
connection.setAutoCommit(false);
}
catch (SQLException ex)
{
throw new DBException(ex, "SET AUTO COMMIT = false");
}
return connection;
}
public void setDBConnectionProvider(IDBConnectionProvider dbConnectionProvider)
{
this.dbConnectionProvider = dbConnectionProvider;
}
public IMetaDataManager getMetaDataManager()
{
return metaDataManager;
}
public DurableLockingManager getDurableLockingManager()
{
return durableLockingManager;
}
public Timer getConnectionKeepAliveTimer()
{
return connectionKeepAliveTimer;
}
@Override
public Set<ChangeFormat> getSupportedChangeFormats()
{
if (mappingStrategy.hasDeltaSupport())
{
return set(ChangeFormat.DELTA);
}
return set(ChangeFormat.REVISION);
}
public ProgressDistributor getAccessorWriteDistributor()
{
return accessorWriteDistributor;
}
public IDBSchema getDBSchema()
{
return database.getSchema();
}
public void visitAllTables(Connection connection, IDBStore.TableVisitor visitor)
{
for (String name : DBUtil.getAllTableNames(connection, getRepository().getName()))
{
try
{
visitor.visitTable(connection, name);
connection.commit();
}
catch (SQLException ex)
{
try
{
connection.rollback();
}
catch (SQLException ex1)
{
throw new DBException(ex1);
}
if (!dbAdapter.isColumnNotFoundException(ex))
{
throw new DBException(ex);
}
}
}
}
public Map<String, String> getPersistentProperties(Set<String> names)
{
IDBConnection connection = database.getConnection();
IDBPreparedStatement stmt = null;
String sql = null;
try
{
Map<String, String> result = new HashMap<String, String>();
boolean allProperties = names == null || names.isEmpty();
if (allProperties)
{
sql = CDODBSchema.SQL_SELECT_ALL_PROPERTIES;
stmt = connection.prepareStatement(sql, ReuseProbability.MEDIUM);
ResultSet resultSet = null;
try
{
resultSet = stmt.executeQuery();
while (resultSet.next())
{
String key = resultSet.getString(1);
String value = resultSet.getString(2);
result.put(key, value);
}
}
finally
{
DBUtil.close(resultSet);
}
}
else
{
sql = CDODBSchema.SQL_SELECT_PROPERTIES;
stmt = connection.prepareStatement(sql, ReuseProbability.MEDIUM);
for (String name : names)
{
stmt.setString(1, name);
ResultSet resultSet = null;
try
{
resultSet = stmt.executeQuery();
if (resultSet.next())
{
String value = resultSet.getString(1);
result.put(name, value);
}
}
finally
{
DBUtil.close(resultSet);
}
}
}
return result;
}
catch (SQLException ex)
{
throw new DBException(ex, sql);
}
finally
{
DBUtil.close(stmt);
DBUtil.close(connection);
}
}
public void setPersistentProperties(Map<String, String> properties)
{
IDBConnection connection = database.getConnection();
IDBPreparedStatement deleteStmt = connection.prepareStatement(CDODBSchema.SQL_DELETE_PROPERTIES,
ReuseProbability.MEDIUM);
IDBPreparedStatement insertStmt = connection.prepareStatement(CDODBSchema.SQL_INSERT_PROPERTIES,
ReuseProbability.MEDIUM);
String sql = null;
try
{
for (Entry<String, String> entry : properties.entrySet())
{
String name = entry.getKey();
String value = entry.getValue();
sql = CDODBSchema.SQL_DELETE_PROPERTIES;
deleteStmt.setString(1, name);
deleteStmt.executeUpdate();
sql = CDODBSchema.SQL_INSERT_PROPERTIES;
insertStmt.setString(1, name);
insertStmt.setString(2, value);
insertStmt.executeUpdate();
}
sql = null;
connection.commit();
}
catch (SQLException ex)
{
throw new DBException(ex, sql);
}
finally
{
DBUtil.close(insertStmt);
DBUtil.close(deleteStmt);
DBUtil.close(connection);
}
}
public void putPersistentProperty(String key, String value)
{
Map<String, String> map = new HashMap<String, String>();
map.put(key, value);
setPersistentProperties(map);
}
public void removePersistentProperties(Set<String> names)
{
IDBConnection connection = database.getConnection();
IDBPreparedStatement stmt = connection.prepareStatement(CDODBSchema.SQL_DELETE_PROPERTIES, ReuseProbability.MEDIUM);
try
{
for (String name : names)
{
stmt.setString(1, name);
stmt.executeUpdate();
}
connection.commit();
}
catch (SQLException ex)
{
throw new DBException(ex, CDODBSchema.SQL_DELETE_PROPERTIES);
}
finally
{
DBUtil.close(stmt);
DBUtil.close(connection);
}
}
@Override
public DBStoreAccessor getReader(ISession session)
{
return (DBStoreAccessor)super.getReader(session);
}
@Override
public DBStoreAccessor getWriter(ITransaction transaction)
{
return (DBStoreAccessor)super.getWriter(transaction);
}
@Override
protected StoreAccessorPool getReaderPool(ISession session, boolean forReleasing)
{
return readerPool;
}
@Override
protected StoreAccessorPool getWriterPool(IView view, boolean forReleasing)
{
return writerPool;
}
@Override
protected DBStoreAccessor createReader(ISession session) throws DBException
{
return new DBStoreAccessor(this, session);
}
@Override
protected DBStoreAccessor createWriter(ITransaction transaction) throws DBException
{
return new DBStoreAccessor(this, transaction);
}
public Map<CDOBranch, List<CDORevision>> getAllRevisions()
{
final Map<CDOBranch, List<CDORevision>> result = new HashMap<CDOBranch, List<CDORevision>>();
InternalSession session = null;
if (!StoreThreadLocal.hasSession())
{
session = getRepository().getSessionManager().openSession(null);
StoreThreadLocal.setSession(session);
}
try
{
StoreThreadLocal.getAccessor().handleRevisions(null, null, CDOBranchPoint.UNSPECIFIED_DATE, true,
new CDORevisionHandler.Filtered.Undetached(new CDORevisionHandler()
{
public boolean handleRevision(CDORevision revision)
{
CDOBranch branch = revision.getBranch();
List<CDORevision> list = result.get(branch);
if (list == null)
{
list = new ArrayList<CDORevision>();
result.put(branch, list);
}
list.add(revision);
return true;
}
}));
}
finally
{
if (session != null)
{
StoreThreadLocal.release();
session.close();
}
}
return result;
}
public CDOID createObjectID(String val)
{
return idHandler.createCDOID(val);
}
@Deprecated
public boolean isLocal(CDOID id)
{
throw new UnsupportedOperationException();
}
public CDOID getNextCDOID(LongIDStoreAccessor accessor, CDORevision revision)
{
return idHandler.getNextCDOID(revision);
}
public long getCreationTime()
{
return creationTime;
}
public void setCreationTime(long creationTime)
{
this.creationTime = creationTime;
Map<String, String> map = new HashMap<String, String>();
map.put(PROP_REPOSITORY_CREATED, Long.toString(creationTime));
setPersistentProperties(map);
}
public boolean isFirstStart()
{
return firstTime;
}
@Override
protected void doBeforeActivate() throws Exception
{
super.doBeforeActivate();
checkNull(mappingStrategy, Messages.getString("DBStore.2")); //$NON-NLS-1$
checkNull(dbAdapter, Messages.getString("DBStore.1")); //$NON-NLS-1$
checkNull(dbConnectionProvider, Messages.getString("DBStore.0")); //$NON-NLS-1$
}
@Override
protected void doActivate() throws Exception
{
super.doActivate();
InternalRepository repository = getRepository();
IDGenerationLocation idGenerationLocation = repository.getIDGenerationLocation();
if (idGenerationLocation == IDGenerationLocation.CLIENT)
{
idHandler = new UUIDHandler(this);
}
else
{
idHandler = new LongIDHandler(this);
}
setObjectIDTypes(idHandler.getObjectIDTypes());
connectionKeepAliveTimer = new Timer("Connection-Keep-Alive-" + this); //$NON-NLS-1$
if (properties != null)
{
if (idGenerationLocation == IDGenerationLocation.CLIENT)
{
String prop = properties.get(IDBStore.Props.ID_COLUMN_LENGTH);
if (prop != null)
{
idColumnLength = Integer.parseInt(prop);
}
}
configureAccessorPool(readerPool, IDBStore.Props.READER_POOL_CAPACITY);
configureAccessorPool(writerPool, IDBStore.Props.WRITER_POOL_CAPACITY);
}
Connection connection = getConnection();
int schemaVersion;
try
{
if (isDropAllDataOnActivate())
{
OM.LOG.info("Dropping all tables from repository " + repository.getName() + "...");
DBUtil.dropAllTables(connection, null);
connection.commit();
}
schemaVersion = selectSchemaVersion(connection);
if (0 <= schemaVersion && schemaVersion < SCHEMA_VERSION)
{
migrateSchema(schemaVersion);
}
// CDODBSchema.INSTANCE.create(dbAdapter, connection);
connection.commit();
}
finally
{
DBUtil.close(connection);
}
String schemaName = repository.getName();
boolean fixNullableIndexColumns = schemaVersion != FIRST_START
&& schemaVersion < FIRST_VERSION_WITH_NULLABLE_CHECKS;
database = DBUtil.openDatabase(dbAdapter, dbConnectionProvider, schemaName, fixNullableIndexColumns);
IDBSchemaTransaction schemaTransaction = database.openSchemaTransaction();
try
{
schemaTransaction.ensureSchema(CDODBSchema.INSTANCE);
schemaTransaction.commit();
}
finally
{
schemaTransaction.close();
}
LifecycleUtil.activate(idHandler);
LifecycleUtil.activate(metaDataManager);
LifecycleUtil.activate(durableLockingManager);
LifecycleUtil.activate(mappingStrategy);
setRevisionTemporality(mappingStrategy.hasAuditSupport() ? RevisionTemporality.AUDITING : RevisionTemporality.NONE);
setRevisionParallelism(
mappingStrategy.hasBranchingSupport() ? RevisionParallelism.BRANCHING : RevisionParallelism.NONE);
if (schemaVersion == FIRST_START)
{
firstStart();
}
else
{
reStart();
}
putPersistentProperty(PROP_SCHEMA_VERSION, Integer.toString(SCHEMA_VERSION));
}
@Override
protected void doDeactivate() throws Exception
{
LifecycleUtil.deactivate(mappingStrategy);
LifecycleUtil.deactivate(durableLockingManager);
LifecycleUtil.deactivate(metaDataManager);
LifecycleUtil.deactivate(idHandler);
Map<String, String> map = new HashMap<String, String>();
map.put(PROP_GRACEFULLY_SHUT_DOWN, Boolean.TRUE.toString());
map.put(PROP_REPOSITORY_STOPPED, Long.toString(getRepository().getTimeStamp()));
if (getRepository().getIDGenerationLocation() == IDGenerationLocation.STORE)
{
map.put(PROP_NEXT_LOCAL_CDOID, Store.idToString(idHandler.getNextLocalObjectID()));
map.put(PROP_LAST_CDOID, Store.idToString(idHandler.getLastObjectID()));
}
map.put(PROP_LAST_BRANCHID, Integer.toString(getLastBranchID()));
map.put(PROP_LAST_LOCAL_BRANCHID, Integer.toString(getLastLocalBranchID()));
map.put(PROP_LAST_COMMITTIME, Long.toString(getLastCommitTime()));
map.put(PROP_LAST_NONLOCAL_COMMITTIME, Long.toString(getLastNonLocalCommitTime()));
setPersistentProperties(map);
if (readerPool != null)
{
readerPool.dispose();
}
if (writerPool != null)
{
writerPool.dispose();
}
connectionKeepAliveTimer.cancel();
connectionKeepAliveTimer = null;
super.doDeactivate();
}
protected boolean isFirstStart(Set<IDBTable> createdTables)
{
if (createdTables.contains(CDODBSchema.PROPERTIES))
{
return true;
}
Set<String> names = new HashSet<String>();
names.add(PROP_REPOSITORY_CREATED);
Map<String, String> map = getPersistentProperties(names);
return map.get(PROP_REPOSITORY_CREATED) == null;
}
protected void firstStart()
{
InternalRepository repository = getRepository();
setCreationTime(repository.getTimeStamp());
firstTime = true;
}
protected void reStart() throws Exception
{
Set<String> names = new HashSet<String>();
names.add(PROP_REPOSITORY_CREATED);
names.add(PROP_GRACEFULLY_SHUT_DOWN);
Map<String, String> map = getPersistentProperties(names);
creationTime = Long.valueOf(map.get(PROP_REPOSITORY_CREATED));
if (map.containsKey(PROP_GRACEFULLY_SHUT_DOWN))
{
names.clear();
InternalRepository repository = getRepository();
boolean generatingIDs = repository.getIDGenerationLocation() == IDGenerationLocation.STORE;
if (generatingIDs)
{
names.add(PROP_NEXT_LOCAL_CDOID);
names.add(PROP_LAST_CDOID);
}
names.add(PROP_LAST_BRANCHID);
names.add(PROP_LAST_LOCAL_BRANCHID);
names.add(PROP_LAST_COMMITTIME);
names.add(PROP_LAST_NONLOCAL_COMMITTIME);
map = getPersistentProperties(names);
if (generatingIDs)
{
idHandler.setNextLocalObjectID(Store.stringToID(map.get(PROP_NEXT_LOCAL_CDOID)));
idHandler.setLastObjectID(Store.stringToID(map.get(PROP_LAST_CDOID)));
}
setLastBranchID(Integer.valueOf(map.get(PROP_LAST_BRANCHID)));
setLastLocalBranchID(Integer.valueOf(map.get(PROP_LAST_LOCAL_BRANCHID)));
setLastCommitTime(Long.valueOf(map.get(PROP_LAST_COMMITTIME)));
setLastNonLocalCommitTime(Long.valueOf(map.get(PROP_LAST_NONLOCAL_COMMITTIME)));
}
else
{
repairAfterCrash();
}
removePersistentProperties(Collections.singleton(PROP_GRACEFULLY_SHUT_DOWN));
}
protected void repairAfterCrash()
{
String name = getRepository().getName();
OM.LOG.warn(MessageFormat.format(Messages.getString("DBStore.9"), name)); //$NON-NLS-1$
Connection connection = getConnection();
try
{
connection.setAutoCommit(false);
connection.setReadOnly(true);
mappingStrategy.repairAfterCrash(dbAdapter, connection); // Must update the idHandler
boolean storeIDs = getRepository().getIDGenerationLocation() == IDGenerationLocation.STORE;
CDOID lastObjectID = storeIDs ? idHandler.getLastObjectID() : CDOID.NULL;
CDOID nextLocalObjectID = storeIDs ? idHandler.getNextLocalObjectID() : CDOID.NULL;
int branchID = DBUtil.selectMaximumInt(connection, CDODBSchema.BRANCHES_ID);
setLastBranchID(branchID > 0 ? branchID : 0);
int localBranchID = DBUtil.selectMinimumInt(connection, CDODBSchema.BRANCHES_ID);
setLastLocalBranchID(localBranchID < 0 ? localBranchID : 0);
long lastCommitTime = DBUtil.selectMaximumLong(connection, CDODBSchema.COMMIT_INFOS_TIMESTAMP);
setLastCommitTime(lastCommitTime);
long lastNonLocalCommitTime = DBUtil.selectMaximumLong(connection, CDODBSchema.COMMIT_INFOS_TIMESTAMP,
CDOBranch.MAIN_BRANCH_ID + "<=" + CDODBSchema.COMMIT_INFOS_BRANCH);
setLastNonLocalCommitTime(lastNonLocalCommitTime);
if (storeIDs)
{
OM.LOG.info(MessageFormat.format(Messages.getString("DBStore.10"), name, lastObjectID, nextLocalObjectID, //$NON-NLS-1$
getLastBranchID(), getLastCommitTime(), getLastNonLocalCommitTime()));
}
else
{
OM.LOG.info(MessageFormat.format(Messages.getString("DBStore.10b"), name, getLastBranchID(), //$NON-NLS-1$
getLastCommitTime(), getLastNonLocalCommitTime()));
}
}
catch (SQLException e)
{
OM.LOG.error(MessageFormat.format(Messages.getString("DBStore.11"), name), e); //$NON-NLS-1$
throw new DBException(e);
}
finally
{
DBUtil.close(connection);
}
}
protected void configureAccessorPool(StoreAccessorPool pool, String property)
{
if (pool != null)
{
String value = properties.get(property);
if (value != null)
{
int capacity = Integer.parseInt(value);
pool.setCapacity(capacity);
}
}
}
protected int selectSchemaVersion(Connection connection) throws SQLException
{
Statement statement = null;
ResultSet resultSet = null;
try
{
statement = connection.createStatement();
resultSet = statement.executeQuery("SELECT " + CDODBSchema.PROPERTIES_VALUE + " FROM " + CDODBSchema.PROPERTIES
+ " WHERE " + CDODBSchema.PROPERTIES_NAME + "='" + PROP_SCHEMA_VERSION + "'");
if (resultSet.next())
{
String value = resultSet.getString(1);
return Integer.parseInt(value);
}
return 0;
}
catch (SQLException ex)
{
connection.rollback();
if (dbAdapter.isTableNotFoundException(ex))
{
return FIRST_START;
}
throw ex;
}
finally
{
DBUtil.close(resultSet);
DBUtil.close(statement);
}
}
protected void migrateSchema(int fromVersion) throws Exception
{
Connection connection = null;
try
{
connection = getConnection();
for (int version = fromVersion; version < SCHEMA_VERSION; version++)
{
if (SCHEMA_MIGRATORS[version] != null)
{
int nextVersion = version + 1;
OM.LOG.info("Migrating schema from version " + version + " to version " + nextVersion + "...");
SCHEMA_MIGRATORS[version].migrateSchema(this, connection);
}
}
connection.commit();
}
finally
{
DBUtil.close(connection);
}
}
/**
* @author Eike Stepper
*/
private static abstract class SchemaMigrator
{
public abstract void migrateSchema(DBStore store, Connection connection) throws Exception;
}
private static final int FIRST_VERSION_WITH_NULLABLE_CHECKS = 4;
private static final SchemaMigrator NO_MIGRATION_NEEDED = null;
private static final SchemaMigrator NON_AUDIT_MIGRATION = new SchemaMigrator()
{
@Override
public void migrateSchema(DBStore store, Connection connection) throws Exception
{
InternalRepository repository = store.getRepository();
if (!repository.isSupportingAudits())
{
store.visitAllTables(connection, new IDBStore.TableVisitor()
{
public void visitTable(Connection connection, String name) throws SQLException
{
Statement statement = null;
try
{
statement = connection.createStatement();
String from = " FROM " + name + " WHERE " + ATTRIBUTES_VERSION + "<" + CDOBranchVersion.FIRST_VERSION;
statement.executeUpdate("DELETE FROM " + CDODBSchema.CDO_OBJECTS + " WHERE " + ATTRIBUTES_ID
+ " IN (SELECT " + ATTRIBUTES_ID + from + ")");
statement.executeUpdate("DELETE" + from);
}
finally
{
DBUtil.close(statement);
}
}
});
}
}
};
private static final SchemaMigrator LOB_SIZE_MIGRATION = new SchemaMigrator()
{
@Override
public void migrateSchema(DBStore store, final Connection connection) throws Exception
{
Statement statement = null;
try
{
statement = connection.createStatement();
IDBAdapter dbAdapter = store.getDBAdapter();
String sql = dbAdapter.sqlRenameField(CDODBSchema.LOBS_SIZE, "size");
statement.execute(sql);
}
finally
{
DBUtil.close(statement);
}
}
};
private static final SchemaMigrator[] SCHEMA_MIGRATORS = { NO_MIGRATION_NEEDED, NON_AUDIT_MIGRATION,
LOB_SIZE_MIGRATION, NO_MIGRATION_NEEDED };
static
{
if (SCHEMA_MIGRATORS.length != SCHEMA_VERSION)
{
throw new Error("There must be exactly " + SCHEMA_VERSION + " schema migrators provided");
}
}
}