From 3d0ab17c22fc6d48becf04bc0406b785b769d8ad Mon Sep 17 00:00:00 2001 From: Stefan Winkler Date: Mon, 18 Jan 2010 15:06:26 +0000 Subject: [270716] Provide support for branching https://bugs.eclipse.org/bugs/show_bug.cgi?id=270716 --- plugins/org.eclipse.emf.cdo.server.db/plugin.xml | 4 + .../emf/cdo/server/internal/db/CDODBSchema.java | 8 + .../internal/db/SmartPreparedStatementCache.java | 4 + .../horizontal/AbstractFeatureMapTableMapping.java | 117 +- .../horizontal/AbstractListTableMapping.java | 79 +- .../AuditFeatureMapTableMappingWithRanges.java | 1265 ++++++++++++++++++++ .../AuditListTableMappingWithRanges.java | 1067 +++++++++++++++++ .../horizontal/BasicAbstractListTableMapping.java | 52 + .../horizontal/HorizontalAuditClassMapping.java | 135 ++- .../HorizontalAuditMappingStrategyWithRanges.java | 64 + .../server/TransactionCommitContextImpl.java | 10 +- .../org/eclipse/emf/cdo/tests/db/AllTestsDBH2.java | 37 +- .../eclipse/emf/cdo/tests/AllTestsAllConfigs.java | 2 + .../cdo/tests/bugzilla/Bugzilla_298561_Test.java | 233 ++++ .../cdo/transaction/CDOTransactionImpl.java | 42 + 15 files changed, 2969 insertions(+), 150 deletions(-) create mode 100644 plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMappingWithRanges.java create mode 100644 plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditListTableMappingWithRanges.java create mode 100644 plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BasicAbstractListTableMapping.java create mode 100644 plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditMappingStrategyWithRanges.java create mode 100644 plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_298561_Test.java diff --git a/plugins/org.eclipse.emf.cdo.server.db/plugin.xml b/plugins/org.eclipse.emf.cdo.server.db/plugin.xml index c91c8f3d32..780a98b78d 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/plugin.xml +++ b/plugins/org.eclipse.emf.cdo.server.db/plugin.xml @@ -37,5 +37,9 @@ class="org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalBranchingMappingStrategy" type="horizontalBranching"> + + diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/CDODBSchema.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/CDODBSchema.java index eadf834faf..fb3e5af950 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/CDODBSchema.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/CDODBSchema.java @@ -182,6 +182,10 @@ public class CDODBSchema extends DBSchema public static final String LIST_REVISION_VERSION = "cdo_version"; //$NON-NLS-1$ + public static final String LIST_REVISION_VERSION_ADDED = "cdo_version_added"; //$NON-NLS-1$ + + public static final String LIST_REVISION_VERSION_REMOVED = "cdo_version_removed"; //$NON-NLS-1$ + public static final String LIST_REVISION_BRANCH = "cdo_branch"; //$NON-NLS-1$ public static final String LIST_IDX = "cdo_idx"; //$NON-NLS-1$ @@ -195,6 +199,10 @@ public class CDODBSchema extends DBSchema public static final String FEATUREMAP_VERSION = "cdo_version"; //$NON-NLS-1$ + public static final String FEATUREMAP_VERSION_ADDED = "cdo_version_added"; //$NON-NLS-1$ + + public static final String FEATUREMAP_VERSION_REMOVED = "cdo_version_removed"; //$NON-NLS-1$ + public static final String FEATUREMAP_BRANCH = "cdo_branch"; //$NON-NLS-1$ public static final String FEATUREMAP_IDX = "cdo_idx"; //$NON-NLS-1$ diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/SmartPreparedStatementCache.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/SmartPreparedStatementCache.java index e6fedad034..41d5c42d95 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/SmartPreparedStatementCache.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/SmartPreparedStatementCache.java @@ -49,6 +49,10 @@ public class SmartPreparedStatementCache extends AbstractPreparedStatementCache return result; } + /** + * @param ps + * the prepared statement to be released to the cache, or null. + */ public void releasePreparedStatement(PreparedStatement ps) { if (ps != null) // Bug 276926: Silently accept ps == null and do nothing. diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractFeatureMapTableMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractFeatureMapTableMapping.java index 08cf34dcee..4ff583abf9 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractFeatureMapTableMapping.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractFeatureMapTableMapping.java @@ -20,8 +20,8 @@ import org.eclipse.emf.cdo.server.IStoreChunkReader.Chunk; import org.eclipse.emf.cdo.server.db.CDODBUtil; import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; import org.eclipse.emf.cdo.server.db.IDBStoreChunkReader; +import org.eclipse.emf.cdo.server.db.IPreparedStatementCache; import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability; -import org.eclipse.emf.cdo.server.db.mapping.IListMapping; import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy; import org.eclipse.emf.cdo.server.db.mapping.ITypeMapping; import org.eclipse.emf.cdo.server.internal.db.CDODBSchema; @@ -57,19 +57,14 @@ import java.util.Map; /** * This abstract base class provides basic behavior needed for mapping many-valued attributes to tables. - * + * * @author Eike Stepper * @since 3.0 */ -public abstract class AbstractFeatureMapTableMapping implements IListMapping +public abstract class AbstractFeatureMapTableMapping extends BasicAbstractListTableMapping { private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, AbstractFeatureMapTableMapping.class); - /** - * The feature for this mapping. - */ - private EStructuralFeature feature; - /** * The table of this mapping. */ @@ -90,11 +85,6 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping */ private Map typeMappings; - /** - * The associated mapping strategy. - */ - private IMappingStrategy mappingStrategy; - // --------- SQL strings - see initSqlStrings() ----------------- private String sqlSelectChunksPrefix; @@ -102,18 +92,13 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping protected String sqlInsert; - private EClass containingClass; - private String sqlGetListLastIndex; private List dbTypes; public AbstractFeatureMapTableMapping(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature) { - this.mappingStrategy = mappingStrategy; - this.feature = feature; - containingClass = eClass; - + super(mappingStrategy, eClass, feature); initDBTypes(); initTable(); initSqlStrings(); @@ -122,14 +107,13 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping private void initDBTypes() { // TODO add annotation processing here ... - dbTypes = new ArrayList(TypeMappingFactory.getDefaultFeatureMapDBTypes()); } private void initTable() { - String tableName = mappingStrategy.getTableName(containingClass, feature); - table = mappingStrategy.getStore().getDBSchema().addTable(tableName); + String tableName = getMappingStrategy().getTableName(getContainingClass(), getFeature()); + table = getMappingStrategy().getStore().getDBSchema().addTable(tableName); // add fields for keys (cdo_id, version, feature_id) FieldInfo[] fields = getKeyFields(); @@ -274,21 +258,11 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping sqlInsert = builder.toString(); } - public final EStructuralFeature getFeature() - { - return feature; - } - protected List getDBTypes() { return dbTypes; } - public final EClass getContainingClass() - { - return containingClass; - } - protected final IDBTable getTable() { return table; @@ -331,33 +305,26 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping if (TRACER.isEnabled()) { - TRACER.format("Reading list values for feature {0}.{1} of {2}v{3}", containingClass.getName(), feature.getName(), - revision.getID(), revision.getVersion()); + TRACER.format("Reading list values for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), getFeature() + .getName(), revision.getID(), revision.getVersion()); } + IPreparedStatementCache statementCache = accessor.getStatementCache(); PreparedStatement pstmt = null; ResultSet resultSet = null; try { String sql = sqlSelectChunksPrefix + sqlOrderByIndex; - - pstmt = accessor.getStatementCache().getPreparedStatement(sql, ReuseProbability.HIGH); - + pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.HIGH); setKeyFields(pstmt, revision); - // if (TRACER.isEnabled()) - // { - // TRACER.trace(pstmt.toString()); - // } - if (listChunk != CDORevision.UNCHUNKED) { pstmt.setMaxRows(listChunk); // optimization - don't read unneeded rows. } resultSet = pstmt.executeQuery(); - while ((listChunk == CDORevision.UNCHUNKED || --listChunk >= 0) && resultSet.next()) { Long tag = resultSet.getLong(1); @@ -388,13 +355,13 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping finally { DBUtil.close(resultSet); - accessor.getStatementCache().releasePreparedStatement(pstmt); + statementCache.releasePreparedStatement(pstmt); } if (TRACER.isEnabled()) { - TRACER.format("Reading list values done for feature {0}.{1} of {2}v{3}", containingClass.getName(), feature //$NON-NLS-1$ - .getName(), revision.getID(), revision.getVersion()); + TRACER.format("Reading list values done for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), + getFeature().getName(), revision.getID(), revision.getVersion()); } } @@ -402,7 +369,7 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping { EStructuralFeature modelFeature = getFeatureByTag(tag); - TypeMapping typeMapping = (TypeMapping)mappingStrategy.createValueMapping(modelFeature); + TypeMapping typeMapping = (TypeMapping)getMappingStrategy().createValueMapping(modelFeature); String column = CDODBSchema.FEATUREMAP_VALUE + "_" + typeMapping.getDBType(); tagMap.put(tag, column); @@ -412,7 +379,7 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping /** * Return the last (maximum) list index. (euals to size-1) - * + * * @param accessor * the accessor to use * @param revision @@ -421,22 +388,16 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping */ private int getListLastIndex(IDBStoreAccessor accessor, InternalCDORevision revision) { + IPreparedStatementCache statementCache = accessor.getStatementCache(); PreparedStatement pstmt = null; ResultSet resultSet = null; try { - pstmt = accessor.getStatementCache().getPreparedStatement(sqlGetListLastIndex, ReuseProbability.HIGH); - + pstmt = statementCache.getPreparedStatement(sqlGetListLastIndex, ReuseProbability.HIGH); setKeyFields(pstmt, revision); - // if (TRACER.isEnabled()) - // { - // TRACER.trace(pstmt.toString()); - // } - resultSet = pstmt.executeQuery(); - if (!resultSet.next()) { if (TRACER.isEnabled()) @@ -464,7 +425,7 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping finally { DBUtil.close(resultSet); - accessor.getStatementCache().releasePreparedStatement(pstmt); + statementCache.releasePreparedStatement(pstmt); } } @@ -472,10 +433,11 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping { if (TRACER.isEnabled()) { - TRACER.format("Reading list chunk values for feature {0}.{1} of {2}v{3}", containingClass.getName(), feature //$NON-NLS-1$ - .getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); + TRACER.format("Reading list chunk values for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), + getFeature().getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); } + IPreparedStatementCache statementCache = chunkReader.getAccessor().getStatementCache(); PreparedStatement pstmt = null; ResultSet resultSet = null; @@ -491,7 +453,7 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping builder.append(sqlOrderByIndex); String sql = builder.toString(); - pstmt = chunkReader.getAccessor().getStatementCache().getPreparedStatement(sql, ReuseProbability.LOW); + pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.LOW); setKeyFields(pstmt, chunkReader.getRevision()); resultSet = pstmt.executeQuery(); @@ -538,8 +500,8 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping if (TRACER.isEnabled()) { - TRACER.format("Reading list chunk values done for feature {0}.{1} of {2}v{3}", containingClass.getName(), - getTagByFeature(feature), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); + TRACER.format("Reading list chunk values done for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), + getTagByFeature(getFeature()), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); } } catch (SQLException ex) @@ -549,7 +511,7 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping finally { DBUtil.close(resultSet); - chunkReader.getAccessor().getStatementCache().releasePreparedStatement(pstmt); + statementCache.releasePreparedStatement(pstmt); } } @@ -566,13 +528,14 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping protected final void writeValue(IDBStoreAccessor accessor, CDORevision revision, int idx, Object value) { + IPreparedStatementCache statementCache = accessor.getStatementCache(); PreparedStatement stmt = null; if (TRACER.isEnabled()) { TRACER .format( - "Writing value for feature {0}.{1} index {2} of {3}v{4} : {5}", containingClass.getName(), getTagByFeature(feature), idx, revision.getID(), revision.getVersion(), value); //$NON-NLS-1$ + "Writing value for feature {0}.{1} index {2} of {3}v{4} : {5}", getContainingClass().getName(), getTagByFeature(getFeature()), idx, revision.getID(), revision.getVersion(), value); //$NON-NLS-1$ } try @@ -583,9 +546,7 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping String column = getColumnName(tag); String sql = sqlInsert; - - stmt = accessor.getStatementCache().getPreparedStatement(sql, ReuseProbability.HIGH); - + stmt = statementCache.getPreparedStatement(sql, ReuseProbability.HIGH); setKeyFields(stmt, revision); int stmtIndex = getKeyFields().length + 1; @@ -603,7 +564,6 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping stmt.setInt(stmtIndex++, idx); stmt.setLong(stmtIndex++, tag); - CDODBUtil.sqlUpdate(stmt, true); } catch (SQLException e) @@ -612,18 +572,17 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping } finally { - accessor.getStatementCache().releasePreparedStatement(stmt); + statementCache.releasePreparedStatement(stmt); } } /** * Get column name (lazy) - * + * * @param tag * The feature's MetaID in CDO * @return the column name where the values are stored */ - protected String getColumnName(Long tag) { String column = tagMap.get(tag); @@ -638,12 +597,11 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping /** * Get type mapping (lazy) - * + * * @param tag * The feature's MetaID in CDO * @return the corresponding type mapping */ - protected ITypeMapping getTypeMapping(Long tag) { ITypeMapping typeMapping = typeMappings.get(tag); @@ -660,10 +618,9 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping * @param metaID * @return the column name where the values are stored */ - private EStructuralFeature getFeatureByTag(Long tag) { - return (EStructuralFeature)mappingStrategy.getStore().getMetaDataManager().getMetaInstance(tag); + return (EStructuralFeature)getMappingStrategy().getStore().getMetaDataManager().getMetaInstance(tag); } /** @@ -671,21 +628,15 @@ public abstract class AbstractFeatureMapTableMapping implements IListMapping * The EStructuralFeature * @return The feature's MetaID in CDO */ - protected Long getTagByFeature(EStructuralFeature feature) { - return mappingStrategy.getStore().getMetaDataManager().getMetaID(feature); + return getMappingStrategy().getStore().getMetaDataManager().getMetaID(feature); } - /** - * @param metaID - * The feature's MetaID in CDO - * @return the column name where the values are stored - */ /** * Used by subclasses to indicate which fields should be in the table. I.e. just a pair of name and DBType ... - * + * * @author Stefan Winkler */ protected static class FieldInfo diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractListTableMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractListTableMapping.java index 0f8d7c8e89..1afc16dfaf 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractListTableMapping.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractListTableMapping.java @@ -18,8 +18,8 @@ import org.eclipse.emf.cdo.server.IStoreChunkReader.Chunk; import org.eclipse.emf.cdo.server.db.CDODBUtil; import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; import org.eclipse.emf.cdo.server.db.IDBStoreChunkReader; +import org.eclipse.emf.cdo.server.db.IPreparedStatementCache; import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability; -import org.eclipse.emf.cdo.server.db.mapping.IListMapping; import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy; import org.eclipse.emf.cdo.server.db.mapping.ITypeMapping; import org.eclipse.emf.cdo.server.internal.db.CDODBSchema; @@ -52,15 +52,10 @@ import java.util.List; * @author Eike Stepper * @since 2.0 */ -public abstract class AbstractListTableMapping implements IListMapping +public abstract class AbstractListTableMapping extends BasicAbstractListTableMapping { private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, AbstractListTableMapping.class); - /** - * The feature for this mapping. - */ - private EStructuralFeature feature; - /** * The table of this mapping. */ @@ -71,11 +66,6 @@ public abstract class AbstractListTableMapping implements IListMapping */ private ITypeMapping typeMapping; - /** - * The associated mapping strategy. - */ - private IMappingStrategy mappingStrategy; - // --------- SQL strings - see initSqlStrings() ----------------- private String sqlSelectChunksPrefix; @@ -83,23 +73,19 @@ public abstract class AbstractListTableMapping implements IListMapping private String sqlInsertEntry; - private EClass containingClass; - private String sqlGetListLastIndex; public AbstractListTableMapping(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature) { - this.mappingStrategy = mappingStrategy; - this.feature = feature; - containingClass = eClass; - + super(mappingStrategy, eClass, feature); initTable(); initSqlStrings(); } private void initTable() { - String tableName = mappingStrategy.getTableName(containingClass, feature); + IMappingStrategy mappingStrategy = getMappingStrategy(); + String tableName = mappingStrategy.getTableName(getContainingClass(), getFeature()); table = mappingStrategy.getStore().getDBSchema().addTable(tableName); // add fields for keys (cdo_id, version, feature_id) @@ -115,7 +101,7 @@ public abstract class AbstractListTableMapping implements IListMapping dbFields[dbFields.length - 1] = table.addField(CDODBSchema.LIST_IDX, DBType.INTEGER); // add field for value - typeMapping = mappingStrategy.createValueMapping(feature); + typeMapping = mappingStrategy.createValueMapping(getFeature()); typeMapping.createDBField(table, CDODBSchema.LIST_VALUE); // add table indexes @@ -212,16 +198,6 @@ public abstract class AbstractListTableMapping implements IListMapping sqlInsertEntry = builder.toString(); } - public final EStructuralFeature getFeature() - { - return feature; - } - - public final EClass getContainingClass() - { - return containingClass; - } - protected final IDBTable getTable() { return table; @@ -254,19 +230,18 @@ public abstract class AbstractListTableMapping implements IListMapping if (TRACER.isEnabled()) { - TRACER.format("Reading list values for feature {0}.{1} of {2}v{3}", containingClass.getName(), feature.getName(), //$NON-NLS-1$ - revision.getID(), revision.getVersion()); + TRACER.format("Reading list values for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), //$NON-NLS-1$ + getFeature().getName(), revision.getID(), revision.getVersion()); } + IPreparedStatementCache statementCache = accessor.getStatementCache(); PreparedStatement pstmt = null; ResultSet resultSet = null; try { String sql = sqlSelectChunksPrefix + sqlOrderByIndex; - - pstmt = accessor.getStatementCache().getPreparedStatement(sql, ReuseProbability.HIGH); - + pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.HIGH); setKeyFields(pstmt, revision); if (TRACER.isEnabled()) @@ -309,13 +284,13 @@ public abstract class AbstractListTableMapping implements IListMapping finally { DBUtil.close(resultSet); - accessor.getStatementCache().releasePreparedStatement(pstmt); + statementCache.releasePreparedStatement(pstmt); } if (TRACER.isEnabled()) { - TRACER.format("Reading list values done for feature {0}.{1} of {2}v{3}", containingClass.getName(), feature //$NON-NLS-1$ - .getName(), revision.getID(), revision.getVersion()); + TRACER.format("Reading list values done for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), //$NON-NLS-1$ + getFeature().getName(), revision.getID(), revision.getVersion()); } } @@ -330,13 +305,13 @@ public abstract class AbstractListTableMapping implements IListMapping */ private int getListLastIndex(IDBStoreAccessor accessor, InternalCDORevision revision) { + IPreparedStatementCache statementCache = accessor.getStatementCache(); PreparedStatement pstmt = null; ResultSet resultSet = null; try { - pstmt = accessor.getStatementCache().getPreparedStatement(sqlGetListLastIndex, ReuseProbability.HIGH); - + pstmt = statementCache.getPreparedStatement(sqlGetListLastIndex, ReuseProbability.HIGH); setKeyFields(pstmt, revision); if (TRACER.isEnabled()) @@ -373,7 +348,7 @@ public abstract class AbstractListTableMapping implements IListMapping finally { DBUtil.close(resultSet); - accessor.getStatementCache().releasePreparedStatement(pstmt); + statementCache.releasePreparedStatement(pstmt); } } @@ -381,10 +356,11 @@ public abstract class AbstractListTableMapping implements IListMapping { if (TRACER.isEnabled()) { - TRACER.format("Reading list chunk values for feature {0}.{1} of {2}v{3}", containingClass.getName(), feature //$NON-NLS-1$ - .getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); + TRACER.format("Reading list chunk values for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), //$NON-NLS-1$ + getFeature().getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); } + IPreparedStatementCache statementCache = chunkReader.getAccessor().getStatementCache(); PreparedStatement pstmt = null; ResultSet resultSet = null; @@ -400,7 +376,7 @@ public abstract class AbstractListTableMapping implements IListMapping builder.append(sqlOrderByIndex); String sql = builder.toString(); - pstmt = chunkReader.getAccessor().getStatementCache().getPreparedStatement(sql, ReuseProbability.LOW); + pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.LOW); setKeyFields(pstmt, chunkReader.getRevision()); resultSet = pstmt.executeQuery(); @@ -446,8 +422,8 @@ public abstract class AbstractListTableMapping implements IListMapping if (TRACER.isEnabled()) { - TRACER.format("Reading list chunk values done for feature {0}.{1} of {2}v{3}", containingClass.getName(), //$NON-NLS-1$ - feature.getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); + TRACER.format("Reading list chunk values done for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), //$NON-NLS-1$ + getFeature().getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); } } catch (SQLException ex) @@ -457,7 +433,7 @@ public abstract class AbstractListTableMapping implements IListMapping finally { DBUtil.close(resultSet); - chunkReader.getAccessor().getStatementCache().releasePreparedStatement(pstmt); + statementCache.releasePreparedStatement(pstmt); } } @@ -474,17 +450,18 @@ public abstract class AbstractListTableMapping implements IListMapping protected final void writeValue(IDBStoreAccessor accessor, CDORevision revision, int idx, Object value) { + IPreparedStatementCache statementCache = accessor.getStatementCache(); PreparedStatement stmt = null; if (TRACER.isEnabled()) { - TRACER.format("Writing value for feature {0}.{1} index {2} of {3}v{4} : {5}", containingClass.getName(), feature //$NON-NLS-1$ - .getName(), idx, revision.getID(), revision.getVersion(), value); + TRACER.format("Writing value for feature {0}.{1} index {2} of {3}v{4} : {5}", getContainingClass().getName(), + getFeature().getName(), idx, revision.getID(), revision.getVersion(), value); } try { - stmt = accessor.getStatementCache().getPreparedStatement(sqlInsertEntry, ReuseProbability.HIGH); + stmt = statementCache.getPreparedStatement(sqlInsertEntry, ReuseProbability.HIGH); setKeyFields(stmt, revision); int stmtIndex = getKeyFields().length + 1; @@ -499,7 +476,7 @@ public abstract class AbstractListTableMapping implements IListMapping } finally { - accessor.getStatementCache().releasePreparedStatement(stmt); + statementCache.releasePreparedStatement(stmt); } } diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMappingWithRanges.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMappingWithRanges.java new file mode 100644 index 0000000000..fcfb591140 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMappingWithRanges.java @@ -0,0 +1,1265 @@ +/** + * Copyright (c) 2004 - 2009 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 271444: [DB] Multiple refactorings bug 271444 + * Christopher Albert - Bug 254455: [DB] Support FeatureMaps bug 254455 + * Victor Roldan Betancort - Bug 283998: [DB] Chunk reading for multiple chunks fails + * Lothar Werzinger - Bug 296440: [DB] Change RDB schema to improve scalability of to-many references in audit mode + * Stefan Winkler - cleanup, merge and maintenance * + */ +package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal; + +import org.eclipse.emf.cdo.common.branch.CDOBranch; +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.id.CDOIDUtil; +import org.eclipse.emf.cdo.common.revision.CDOList; +import org.eclipse.emf.cdo.common.revision.CDORevision; +import org.eclipse.emf.cdo.common.revision.CDORevisionUtil; +import org.eclipse.emf.cdo.common.revision.delta.CDOAddFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOContainerFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDeltaVisitor; +import org.eclipse.emf.cdo.common.revision.delta.CDOListFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOMoveFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta; +import org.eclipse.emf.cdo.server.IRepository; +import org.eclipse.emf.cdo.server.IStoreChunkReader.Chunk; +import org.eclipse.emf.cdo.server.db.CDODBUtil; +import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; +import org.eclipse.emf.cdo.server.db.IDBStoreChunkReader; +import org.eclipse.emf.cdo.server.db.IPreparedStatementCache; +import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability; +import org.eclipse.emf.cdo.server.db.mapping.IListMappingDeltaSupport; +import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy; +import org.eclipse.emf.cdo.server.db.mapping.ITypeMapping; +import org.eclipse.emf.cdo.server.internal.db.CDODBSchema; +import org.eclipse.emf.cdo.server.internal.db.bundle.OM; +import org.eclipse.emf.cdo.server.internal.db.mapping.TypeMapping; +import org.eclipse.emf.cdo.server.internal.db.mapping.TypeMappingFactory; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDOList; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; + +import org.eclipse.net4j.db.DBException; +import org.eclipse.net4j.db.DBType; +import org.eclipse.net4j.db.DBUtil; +import org.eclipse.net4j.db.ddl.IDBField; +import org.eclipse.net4j.db.ddl.IDBTable; +import org.eclipse.net4j.db.ddl.IDBIndex.Type; +import org.eclipse.net4j.util.ImplementationError; +import org.eclipse.net4j.util.collection.MoveableList; +import org.eclipse.net4j.util.om.trace.ContextTracer; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.util.FeatureMap; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * This is a featuremap-table mapping for audit mode. It is optimized for frequent insert operations at the list's end, + * which causes just 1 DB row to be changed. This is achieved by introducing a version range (columns + * {@link CDODBSchema#LIST_REVISION_VERSION_ADDED cdo_version_added} and + * {@link CDODBSchema#LIST_REVISION_VERSION_REMOVED cdo_version_removed}) which records for which revisions a particular + * entry existed. Also, this mapping is mainly optimized for potentially very large lists: the need for having the + * complete list stored in memory to do in-the-middle-moved and inserts is traded in for a few more DB access + * operations. + * + * @author Eike Stepper + * @author Stefan Winkler + * @author Lothar Werzinger + * @since 3.0 + */ +public class AuditFeatureMapTableMappingWithRanges extends BasicAbstractListTableMapping implements + IListMappingDeltaSupport +{ + private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, AuditFeatureMapTableMappingWithRanges.class); + + /** + * Used to clean up lists for detached objects. + */ + private static final int FINAL_VERSION = Integer.MAX_VALUE; + + /** + * The table of this mapping. + */ + private IDBTable table; + + /** + * The tags mapped to column names + */ + private HashMap tagMap; + + /** + * Column name Set + */ + private List columnNames; + + /** + * The type mappings for the value fields. + */ + private Map typeMappings; + + // --------- SQL strings - see initSqlStrings() ----------------- + private String sqlSelectChunksPrefix; + + private String sqlOrderByIndex; + + protected String sqlInsert; + + private String sqlGetListLastIndex; + + private List dbTypes; + + private String sqlRemoveEntry; + + private String sqlDeleteEntry; + + private String sqlUpdateIndex; + + private String sqlGetValue; + + private String sqlClearList; + + private String sqlDeleteList; + + public AuditFeatureMapTableMappingWithRanges(IMappingStrategy mappingStrategy, EClass eClass, + EStructuralFeature feature) + { + super(mappingStrategy, eClass, feature); + initDBTypes(); + initTable(); + initSqlStrings(); + } + + private void initDBTypes() + { + // TODO add annotation processing here ... + dbTypes = new ArrayList(TypeMappingFactory.getDefaultFeatureMapDBTypes()); + } + + private void initTable() + { + String tableName = getMappingStrategy().getTableName(getContainingClass(), getFeature()); + table = getMappingStrategy().getStore().getDBSchema().addTable(tableName); + + // add fields for CDOID + IDBField idField = table.addField(CDODBSchema.FEATUREMAP_REVISION_ID, DBType.INTEGER); + + // add fields for version range + IDBField versionAddedField = table.addField(CDODBSchema.FEATUREMAP_VERSION_ADDED, DBType.INTEGER); + IDBField versionRemovedField = table.addField(CDODBSchema.FEATUREMAP_VERSION_REMOVED, DBType.INTEGER); + + // add field for list index + IDBField idxField = table.addField(CDODBSchema.FEATUREMAP_IDX, DBType.INTEGER); + + // add field for FeatureMap tag (MetaID for Feature in CDO registry) + IDBField tagField = table.addField(CDODBSchema.FEATUREMAP_TAG, DBType.INTEGER); + + tagMap = new HashMap(); + typeMappings = new HashMap(); + columnNames = new ArrayList(); + + // create columns for all DBTypes + for (DBType type : getDBTypes()) + { + String column = CDODBSchema.FEATUREMAP_VALUE + "_" + type.name(); + table.addField(column, type); + columnNames.add(column); + } + + // TODO think about indices + table.addIndex(Type.NON_UNIQUE, idField); + table.addIndex(Type.NON_UNIQUE, versionAddedField); + table.addIndex(Type.NON_UNIQUE, versionRemovedField); + table.addIndex(Type.NON_UNIQUE, idxField); + table.addIndex(Type.NON_UNIQUE, tagField); + } + + public Collection getDBTables() + { + return Arrays.asList(table); + } + + private void initSqlStrings() + { + String tableName = getTable().getName(); + + // ---------------- SELECT to read chunks ---------------------------- + StringBuilder builder = new StringBuilder(); + builder.append("SELECT "); + + builder.append(CDODBSchema.FEATUREMAP_TAG); + builder.append(", "); + + Iterator iter = columnNames.iterator(); + while (iter.hasNext()) + { + builder.append(iter.next()); + if (iter.hasNext()) + { + builder.append(", "); + } + } + + builder.append(" FROM "); + builder.append(tableName); + builder.append(" WHERE "); + builder.append(CDODBSchema.FEATUREMAP_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED); + builder.append(" <= ? AND ( "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" IS NULL OR "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" > ? )"); //$NON-NLS-1$ + sqlSelectChunksPrefix = builder.toString(); + + sqlOrderByIndex = " ORDER BY " + CDODBSchema.FEATUREMAP_IDX; //$NON-NLS-1$ + + // ----------------- count list size -------------------------- + + builder = new StringBuilder("SELECT count(1) FROM "); + builder.append(tableName); + builder.append(" WHERE "); + builder.append(CDODBSchema.FEATUREMAP_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED); + builder.append(" <= ? AND ( "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" IS NULL OR "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" > ? )"); //$NON-NLS-1$ + sqlGetListLastIndex = builder.toString(); + + // ----------------- INSERT - prefix ----------------- + builder = new StringBuilder("INSERT INTO "); + builder.append(tableName); + builder.append("("); + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(","); + builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED); + builder.append(","); + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(","); + builder.append(CDODBSchema.LIST_IDX); + builder.append(","); + builder.append(CDODBSchema.LIST_VALUE); + + for (int i = 0; i < columnNames.size(); i++) + { + builder.append(columnNames.get(i)); + builder.append(", "); //$NON-NLS-1$ + } + + builder.append(CDODBSchema.FEATUREMAP_IDX); + builder.append(", "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_TAG); + builder.append(") VALUES (?, ?, ?, ?, ?, "); //$NON-NLS-1$ + for (int i = 0; i < columnNames.size(); i++) + { + builder.append("?, "); + } + + builder.append("?, ?)"); + sqlInsert = builder.toString(); + + // ----------------- remove current entry ----------------- + builder = new StringBuilder("UPDATE "); //$NON-NLS-1$ + builder.append(tableName); + builder.append(" SET "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" = ? "); //$NON-NLS-1$ + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_IDX); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" IS NULL"); //$NON-NLS-1$ + sqlRemoveEntry = builder.toString(); + + // ----------------- delete temporary entry ----------------- + builder = new StringBuilder("DELETE FROM "); //$NON-NLS-1$ + builder.append(tableName); + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_IDX); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED); + builder.append(" = ?"); //$NON-NLS-1$ + sqlDeleteEntry = builder.toString(); + + // ----------------- update index ----------------- + builder = new StringBuilder("UPDATE "); //$NON-NLS-1$ + builder.append(tableName); + builder.append(" SET "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_IDX); + builder.append(" = ? WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_IDX); + builder.append(" = ?"); //$NON-NLS-1$ + sqlUpdateIndex = builder.toString(); + + // ----------------- get current value ----------------- + builder = new StringBuilder("SELECT "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_TAG); + builder.append(", "); + + iter = columnNames.iterator(); + while (iter.hasNext()) + { + builder.append(iter.next()); + if (iter.hasNext()) + { + builder.append(", "); + } + } + + builder.append(" FROM "); //$NON-NLS-1$ + builder.append(tableName); + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_IDX); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" IS NULL"); //$NON-NLS-1$ + sqlGetValue = builder.toString(); + + // ----------- clear list items ------------------------- + builder = new StringBuilder("UPDATE "); //$NON-NLS-1$ + builder.append(tableName); + builder.append(" SET "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" = ? "); //$NON-NLS-1$ + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" IS NULL"); //$NON-NLS-1$ + sqlClearList = builder.toString(); + + // ----------- delete temporary list items ------------------------- + builder = new StringBuilder("DELETE FROM "); //$NON-NLS-1$ + builder.append(tableName); + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED); + builder.append(" IS NULL"); //$NON-NLS-1$ + sqlDeleteList = builder.toString(); + } + + protected List getDBTypes() + { + return dbTypes; + } + + protected final IDBTable getTable() + { + return table; + } + + protected final List getColumnNames() + { + return columnNames; + } + + protected final Map getTypeMappings() + { + return typeMappings; + } + + protected final Map getTagMap() + { + return tagMap; + } + + public void readValues(IDBStoreAccessor accessor, InternalCDORevision revision, int listChunk) + { + MoveableList list = revision.getList(getFeature()); + int listSize = -1; + + if (listChunk != CDORevision.UNCHUNKED) + { + listSize = getListLastIndex(accessor, revision); + if (listSize == -1) + { + // list is empty - take shortcut + return; + } + + // subtract amount of items we are going to read now + listSize -= listChunk; + } + + if (TRACER.isEnabled()) + { + TRACER.format("Reading list values for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), getFeature() + .getName(), revision.getID(), revision.getVersion()); + } + + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + ResultSet resultSet = null; + + try + { + String sql = sqlSelectChunksPrefix + sqlOrderByIndex; + + pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.HIGH); + + pstmt.setLong(1, CDOIDUtil.getLong(revision.getID())); + pstmt.setInt(2, revision.getVersion()); + pstmt.setInt(3, revision.getVersion()); + + if (listChunk != CDORevision.UNCHUNKED) + { + pstmt.setMaxRows(listChunk); // optimization - don't read unneeded rows. + } + + resultSet = pstmt.executeQuery(); + while ((listChunk == CDORevision.UNCHUNKED || --listChunk >= 0) && resultSet.next()) + { + Long tag = resultSet.getLong(1); + Object value = getTypeMapping(tag).readValue(resultSet); + + if (TRACER.isEnabled()) + { + TRACER.format("Read value for index {0} from result set: {1}", list.size(), value); + } + + list.add(CDORevisionUtil.createFeatureMapEntry(getFeatureByTag(tag), value)); + } + + while (listSize-- >= 0) + { + if (TRACER.isEnabled()) + { + TRACER.format("Adding UNINITIALIZED for index {0} ", list.size()); + } + + list.add(InternalCDOList.UNINITIALIZED); + } + } + catch (SQLException ex) + { + throw new DBException(ex); + } + finally + { + DBUtil.close(resultSet); + statementCache.releasePreparedStatement(pstmt); + } + + if (TRACER.isEnabled()) + { + TRACER.format("Reading list values done for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), + getFeature().getName(), revision.getID(), revision.getVersion()); + } + } + + private void addFeature(Long tag) + { + EStructuralFeature modelFeature = getFeatureByTag(tag); + + TypeMapping typeMapping = (TypeMapping)getMappingStrategy().createValueMapping(modelFeature); + String column = CDODBSchema.FEATUREMAP_VALUE + "_" + typeMapping.getDBType(); + + tagMap.put(tag, column); + typeMapping.setDBField(table, column); + typeMappings.put(tag, typeMapping); + } + + /** + * Return the last (maximum) list index. (euals to size-1) + * + * @param accessor + * the accessor to use + * @param revision + * the revision to which the feature list belongs + * @return the last index or -1 if the list is empty. + */ + private int getListLastIndex(IDBStoreAccessor accessor, InternalCDORevision revision) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + ResultSet resultSet = null; + + try + { + pstmt = statementCache.getPreparedStatement(sqlGetListLastIndex, ReuseProbability.HIGH); + + pstmt.setLong(1, CDOIDUtil.getLong(revision.getID())); + pstmt.setInt(2, revision.getVersion()); + pstmt.setInt(3, revision.getVersion()); + + resultSet = pstmt.executeQuery(); + if (!resultSet.next()) + { + throw new DBException("count expects exactly one result."); + } + else + { + int result = resultSet.getInt(1) - 1; + if (TRACER.isEnabled()) + { + TRACER.trace("Read list last index = " + result); + } + + return result; + } + } + catch (SQLException ex) + { + throw new DBException(ex); + } + finally + { + DBUtil.close(resultSet); + statementCache.releasePreparedStatement(pstmt); + } + } + + public final void readChunks(IDBStoreChunkReader chunkReader, List chunks, String where) + { + if (TRACER.isEnabled()) + { + TRACER.format("Reading list chunk values for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), + getFeature().getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); + } + + IPreparedStatementCache statementCache = chunkReader.getAccessor().getStatementCache(); + PreparedStatement pstmt = null; + ResultSet resultSet = null; + + try + { + StringBuilder builder = new StringBuilder(sqlSelectChunksPrefix); + if (where != null) + { + builder.append(" AND "); //$NON-NLS-1$ + builder.append(where); + } + + builder.append(sqlOrderByIndex); + + String sql = builder.toString(); + pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.LOW); + pstmt.setLong(1, CDOIDUtil.getLong(chunkReader.getRevision().getID())); + pstmt.setInt(2, chunkReader.getRevision().getVersion()); + pstmt.setInt(3, chunkReader.getRevision().getVersion()); + + resultSet = pstmt.executeQuery(); + + Chunk chunk = null; + int chunkSize = 0; + int chunkIndex = 0; + int indexInChunk = 0; + + while (resultSet.next()) + { + Long tag = resultSet.getLong(1); + Object value = getTypeMapping(tag).readValue(resultSet); + + if (chunk == null) + { + chunk = chunks.get(chunkIndex++); + chunkSize = chunk.size(); + + if (TRACER.isEnabled()) + { + TRACER.format("Current chunk no. {0} is [start = {1}, size = {2}]", chunkIndex - 1, chunk.getStartIndex(), + chunkSize); + } + } + + if (TRACER.isEnabled()) + { + TRACER.format("Read value for chunk index {0} from result set: {1}", indexInChunk, value); + } + + chunk.add(indexInChunk++, CDORevisionUtil.createFeatureMapEntry(getFeatureByTag(tag), value)); + if (indexInChunk == chunkSize) + { + if (TRACER.isEnabled()) + { + TRACER.format("Chunk finished."); + } + + chunk = null; + indexInChunk = 0; + } + } + + if (TRACER.isEnabled()) + { + TRACER.format("Reading list chunk values done for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), + getTagByFeature(getFeature()), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); + } + } + catch (SQLException ex) + { + throw new DBException(ex); + } + finally + { + DBUtil.close(resultSet); + statementCache.releasePreparedStatement(pstmt); + } + } + + public void writeValues(IDBStoreAccessor accessor, InternalCDORevision revision) + { + CDOList values = revision.getList(getFeature()); + + int idx = 0; + for (Object element : values) + { + writeValue(accessor, revision, idx++, element); + } + + if (TRACER.isEnabled()) + { + TRACER.format("Writing done"); + } + } + + protected final void writeValue(IDBStoreAccessor accessor, CDORevision revision, int idx, Object value) + { + if (TRACER.isEnabled()) + { + TRACER + .format( + "Writing value for feature {0}.{1} index {2} of {3}v{4} : {5}", getContainingClass().getName(), getTagByFeature(getFeature()), idx, revision.getID(), revision.getVersion(), value); //$NON-NLS-1$ + } + + addEntry(accessor, revision.getID(), revision.getVersion(), idx, value); + } + + /** + * Get column name (lazy). + * + * @param tag + * The feature's MetaID in CDO + * @return the column name where the values are stored + */ + protected String getColumnName(Long tag) + { + String column = tagMap.get(tag); + if (column == null) + { + addFeature(tag); + column = tagMap.get(tag); + } + + return column; + } + + /** + * Get type mapping (lazy). + * + * @param tag + * The feature's MetaID in CDO + * @return the corresponding type mapping + */ + protected ITypeMapping getTypeMapping(Long tag) + { + ITypeMapping typeMapping = typeMappings.get(tag); + if (typeMapping == null) + { + addFeature(tag); + typeMapping = typeMappings.get(tag); + } + + return typeMapping; + } + + /** + * @param metaID + * @return the column name where the values are stored + */ + private EStructuralFeature getFeatureByTag(Long tag) + { + return (EStructuralFeature)getMappingStrategy().getStore().getMetaDataManager().getMetaInstance(tag); + } + + /** + * @param feature + * The EStructuralFeature + * @return The feature's MetaID in CDO + */ + protected Long getTagByFeature(EStructuralFeature feature) + { + return getMappingStrategy().getStore().getMetaDataManager().getMetaID(feature); + } + + /** + * Clear a list of a given revision. + * + * @param accessor + * the accessor to use + * @param id + * the id of the revision from which to remove all items + */ + public void clearList(IDBStoreAccessor accessor, CDOID id, int oldVersion, int newVersion) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmtDeleteTemp = null; + PreparedStatement pstmtClear = null; + + try + { + // delete temporary entries + pstmtDeleteTemp = statementCache.getPreparedStatement(sqlDeleteList, ReuseProbability.HIGH); + pstmtDeleteTemp.setLong(1, CDOIDUtil.getLong(id)); + pstmtDeleteTemp.setInt(2, newVersion); + + int result = CDODBUtil.sqlUpdate(pstmtDeleteTemp, false); + if (TRACER.isEnabled()) + { + TRACER.format("DeleteList result: {0}", result); //$NON-NLS-1$ + } + + // clear rest of the list + pstmtClear = statementCache.getPreparedStatement(sqlClearList, ReuseProbability.HIGH); + pstmtClear.setInt(1, newVersion); + pstmtClear.setLong(2, CDOIDUtil.getLong(id)); + + result = CDODBUtil.sqlUpdate(pstmtClear, false); + if (TRACER.isEnabled()) + { + TRACER.format("ClearList result: {0}", result); //$NON-NLS-1$ + } + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmtDeleteTemp); + statementCache.releasePreparedStatement(pstmtClear); + } + } + + public void objectRevised(IDBStoreAccessor accessor, CDOID id, long revised) + { + if (TRACER.isEnabled()) + { + TRACER.format("objectRevised {0} {1}", id, revised); + } + + CDOBranch main = getMappingStrategy().getStore().getRepository().getBranchManager().getMainBranch(); + + // get revision from cache to find out version number + CDORevision revision = getMappingStrategy().getStore().getRepository().getRevisionManager().getRevision(id, + main.getHead(), /* chunksize = */0, CDORevision.DEPTH_NONE, true); + + // set cdo_revision_removed for all list items (so we have no NULL values) + clearList(accessor, id, revision.getVersion(), FINAL_VERSION); + } + + public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int oldVersion, final int newVersion, + long created, CDOListFeatureDelta delta) + { + IRepository repo = accessor.getStore().getRepository(); + InternalCDORevision originalRevision = (InternalCDORevision)repo.getRevisionManager().getRevision(id, + repo.getBranchManager().getMainBranch().getHead(), /* chunksize = */0, CDORevision.DEPTH_NONE, true); + + int oldListSize = originalRevision.getList(getFeature()).size(); + + if (TRACER.isEnabled()) + { + TRACER.format("ListTableMapping.processDelta for revision {0} - previous list size: {1}", originalRevision, + oldListSize); + } + + // let the visitor collect the changes + ListDeltaVisitor visitor = new ListDeltaVisitor(accessor, originalRevision, oldVersion, newVersion); + + if (TRACER.isEnabled()) + { + TRACER.format("processing deltas ..."); + } + + for (CDOFeatureDelta listDelta : delta.getListChanges()) + { + listDelta.accept(visitor); + } + } + + private class ListDeltaVisitor implements CDOFeatureDeltaVisitor + { + private IDBStoreAccessor accessor; + + private InternalCDORevision originalRevision; + + private CDOID id; + + private int oldVersion; + + private int newVersion; + + private int lastIndex; + + public ListDeltaVisitor(IDBStoreAccessor accessor, InternalCDORevision originalRevision, int oldVersion, + int newVersion) + { + this.accessor = accessor; + this.originalRevision = originalRevision; + id = this.originalRevision.getID(); + this.oldVersion = oldVersion; + this.newVersion = newVersion; + lastIndex = originalRevision.getList(getFeature()).size() - 1; + } + + public void visit(CDOMoveFeatureDelta delta) + { + int fromIdx = delta.getOldPosition(); + int toIdx = delta.getNewPosition(); + + if (TRACER.isEnabled()) + { + TRACER.format("Delta Moving: {0} to {1}", fromIdx, toIdx); //$NON-NLS-1$ + } + + Object value = getValue(accessor, id, fromIdx); + + // remove the item + removeEntry(accessor, id, oldVersion, newVersion, fromIdx); + + // adjust indexes and shift either up or down + if (fromIdx < toIdx) + { + moveOneUp(accessor, id, oldVersion, newVersion, fromIdx + 1, toIdx); + } + else + { // fromIdx > toIdx here + moveOneDown(accessor, id, oldVersion, newVersion, toIdx, fromIdx - 1); + } + + // create the item + addEntry(accessor, id, newVersion, toIdx, value); + } + + public void visit(CDOAddFeatureDelta delta) + { + int startIndex = delta.getIndex(); + int endIndex = lastIndex; + + if (TRACER.isEnabled()) + { + TRACER.format("Delta Adding at: {0}", startIndex); //$NON-NLS-1$ + } + + if (startIndex <= endIndex) + { + // make room for the new item + moveOneDown(accessor, id, oldVersion, newVersion, startIndex, endIndex); + } + + // create the item + addEntry(accessor, id, newVersion, startIndex, delta.getValue()); + + ++lastIndex; + } + + public void visit(CDORemoveFeatureDelta delta) + { + int startIndex = delta.getIndex(); + int endIndex = lastIndex; + + if (TRACER.isEnabled()) + { + TRACER.format("Delta Removing at: {0}", startIndex); //$NON-NLS-1$ + } + + // remove the item + removeEntry(accessor, id, oldVersion, newVersion, startIndex); + + // make room for the new item + moveOneUp(accessor, id, oldVersion, newVersion, startIndex + 1, endIndex); + + --lastIndex; + } + + public void visit(CDOSetFeatureDelta delta) + { + int index = delta.getIndex(); + + if (TRACER.isEnabled()) + { + TRACER.format("Delta Setting at: {0}", index); //$NON-NLS-1$ + } + + // remove the item + removeEntry(accessor, id, oldVersion, newVersion, index); + + // create the item + addEntry(accessor, id, newVersion, index, delta.getValue()); + } + + public void visit(CDOUnsetFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + public void visit(CDOListFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + public void visit(CDOClearFeatureDelta delta) + { + if (TRACER.isEnabled()) + { + TRACER.format("Delta Clearing"); //$NON-NLS-1$ + } + clearList(accessor, id, oldVersion, newVersion); + + lastIndex = -1; + } + + public void visit(CDOContainerFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + private void moveOneUp(IDBStoreAccessor accessor, CDOID id, int oldVersion, int newVersion, int startIndex, + int endIndex) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + + try + { + pstmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH); + + for (int index = startIndex; index <= endIndex; ++index) + { + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp moving: {0} -> {1}", index, index - 1); //$NON-NLS-1$ + } + + int stmtIndex = 1; + pstmt.setInt(stmtIndex++, index - 1); + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, newVersion); + pstmt.setInt(stmtIndex++, index); + + int result = CDODBUtil.sqlUpdate(pstmt, false); + switch (result) + { + case 0: + Object value = getValue(accessor, id, index); + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp remove: {0}", index); //$NON-NLS-1$ + } + + removeEntry(accessor, id, oldVersion, newVersion, index); + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp add: {0}", index - 1); //$NON-NLS-1$ + } + + addEntry(accessor, id, newVersion, index - 1, value); + break; + + case 1: + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp updated: {0} -> {1}", index, index - 1); //$NON-NLS-1$ + } + + break; + + default: + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp Too many results: {0} -> {1}: {2}", index, index + 1, result); //$NON-NLS-1$ + } + + throw new DBException("Too many results"); //$NON-NLS-1$ + } + } + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + } + + private void moveOneDown(IDBStoreAccessor accessor, CDOID id, int oldVersion, int newVersion, int startIndex, + int endIndex) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + + try + { + pstmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH); + for (int index = endIndex; index >= startIndex; --index) + { + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown moving: {0} -> {1}", index, index + 1); //$NON-NLS-1$ + } + + int stmtIndex = 1; + pstmt.setInt(stmtIndex++, index + 1); + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, newVersion); + pstmt.setInt(stmtIndex++, index); + + int result = CDODBUtil.sqlUpdate(pstmt, false); + switch (result) + { + case 0: + Object value = getValue(accessor, id, index); + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown remove: {0}", index); //$NON-NLS-1$ + } + + removeEntry(accessor, id, oldVersion, newVersion, index); + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown add: {0}", index + 1); //$NON-NLS-1$ + } + + addEntry(accessor, id, newVersion, index + 1, value); + break; + + case 1: + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown updated: {0} -> {1}", index, index + 1); //$NON-NLS-1$ + } + + break; + + default: + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown Too many results: {0} -> {1}: {2}", index, index + 1, result); //$NON-NLS-1$ + } + + throw new DBException("Too many results"); //$NON-NLS-1$ + } + } + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + } + } + + private void addEntry(IDBStoreAccessor accessor, CDOID id, int version, int index, Object value) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + + if (TRACER.isEnabled()) + { + TRACER.format("Adding value for feature() {0}.{1} index {2} of {3}v{4} : {5}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), index, id, version, value); + } + + try + { + FeatureMap.Entry entry = (FeatureMap.Entry)value; + EStructuralFeature entryFeature = entry.getEStructuralFeature(); + Long tag = getTagByFeature(entryFeature); + String column = getColumnName(tag); + + pstmt = statementCache.getPreparedStatement(sqlInsert, ReuseProbability.HIGH); + + int stmtIndex = 1; + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, version); + pstmt.setInt(stmtIndex++, index); + + for (int i = 0; i < columnNames.size(); i++) + { + if (columnNames.get(i).equals(column)) + { + getTypeMapping(tag).setValue(pstmt, stmtIndex++, entry.getValue()); + } + else + { + pstmt.setNull(stmtIndex++, getDBTypes().get(i).getCode()); + } + } + + pstmt.setInt(stmtIndex++, index); + pstmt.setLong(stmtIndex++, tag); + CDODBUtil.sqlUpdate(pstmt, true); + } + catch (SQLException e) + { + throw new DBException(e); + } + catch (IllegalStateException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + } + + private void removeEntry(IDBStoreAccessor accessor, CDOID id, int oldVersion, int newVersion, int index) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + + if (TRACER.isEnabled()) + { + TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), index, id, newVersion); + } + + try + { + // try to delete a temporary entry first + pstmt = statementCache.getPreparedStatement(sqlDeleteEntry, ReuseProbability.HIGH); + + int stmtIndex = 1; + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, index); + pstmt.setInt(stmtIndex++, newVersion); + + int result = CDODBUtil.sqlUpdate(pstmt, false); + if (result == 1) + { + if (TRACER.isEnabled()) + { + TRACER.format("removeEntry deleted: {0}", index); //$NON-NLS-1$ + } + } + else if (result > 1) + { + if (TRACER.isEnabled()) + { + TRACER.format("removeEntry Too many results: {0}: {1}", index, result); //$NON-NLS-1$ + } + + throw new DBException("Too many results"); //$NON-NLS-1$ + } + else + { + // no temporary entry found, so mark the entry as removed + statementCache.releasePreparedStatement(pstmt); + pstmt = statementCache.getPreparedStatement(sqlRemoveEntry, ReuseProbability.HIGH); + + stmtIndex = 1; + pstmt.setInt(stmtIndex++, newVersion); + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, index); + CDODBUtil.sqlUpdate(pstmt, true); + } + } + catch (SQLException e) + { + if (TRACER.isEnabled()) + { + TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4} FAILED {5}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), index, id, newVersion, e.getMessage()); + } + + throw new DBException(e); + } + catch (IllegalStateException e) + { + if (TRACER.isEnabled()) + { + TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4} FAILED {5}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), index, id, newVersion, e.getMessage()); + } + + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + } + + private FeatureMap.Entry getValue(IDBStoreAccessor accessor, CDOID id, int index) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + FeatureMap.Entry result = null; + + try + { + pstmt = statementCache.getPreparedStatement(sqlGetValue, ReuseProbability.HIGH); + + int stmtIndex = 1; + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, index); + + ResultSet resultSet = pstmt.executeQuery(); + if (!resultSet.next()) + { + throw new DBException("getValue expects exactly one result."); + } + + Long tag = resultSet.getLong(1); + Object value = getTypeMapping(tag).readValue(resultSet); + result = CDORevisionUtil.createFeatureMapEntry(getFeatureByTag(tag), value); + + if (TRACER.isEnabled()) + { + TRACER.format("Read value (index {0}) from result set: {1}", index, result); //$NON-NLS-1$ + } + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + + return result; + } +} diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditListTableMappingWithRanges.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditListTableMappingWithRanges.java new file mode 100644 index 0000000000..24b3f99bf3 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditListTableMappingWithRanges.java @@ -0,0 +1,1067 @@ +/** + * Copyright (c) 2004 - 2009 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 + * + * This class has been derived from AbstractListTableMapping + * + * Contributors: + * Eike Stepper - initial API and implementation + * Lothar Werzinger - Bug 296440: [DB] Change RDB schema to improve scalability of to-many references in audit mode + * Stefan Winkler - cleanup, merge and maintenance + */ +package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal; + +import org.eclipse.emf.cdo.common.branch.CDOBranch; +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.id.CDOIDUtil; +import org.eclipse.emf.cdo.common.revision.CDOList; +import org.eclipse.emf.cdo.common.revision.CDORevision; +import org.eclipse.emf.cdo.common.revision.delta.CDOAddFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOContainerFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDeltaVisitor; +import org.eclipse.emf.cdo.common.revision.delta.CDOListFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOMoveFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta; +import org.eclipse.emf.cdo.server.IRepository; +import org.eclipse.emf.cdo.server.IStoreChunkReader.Chunk; +import org.eclipse.emf.cdo.server.db.CDODBUtil; +import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; +import org.eclipse.emf.cdo.server.db.IDBStoreChunkReader; +import org.eclipse.emf.cdo.server.db.IPreparedStatementCache; +import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability; +import org.eclipse.emf.cdo.server.db.mapping.IListMappingDeltaSupport; +import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy; +import org.eclipse.emf.cdo.server.db.mapping.ITypeMapping; +import org.eclipse.emf.cdo.server.internal.db.CDODBSchema; +import org.eclipse.emf.cdo.server.internal.db.bundle.OM; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDOList; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; + +import org.eclipse.net4j.db.DBException; +import org.eclipse.net4j.db.DBType; +import org.eclipse.net4j.db.DBUtil; +import org.eclipse.net4j.db.ddl.IDBField; +import org.eclipse.net4j.db.ddl.IDBTable; +import org.eclipse.net4j.db.ddl.IDBIndex.Type; +import org.eclipse.net4j.util.ImplementationError; +import org.eclipse.net4j.util.collection.MoveableList; +import org.eclipse.net4j.util.om.trace.ContextTracer; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EStructuralFeature; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * This is a list-table mapping for audit mode. It is optimized for frequent insert operations at the list's end, which + * causes just 1 DB row to be changed. This is achieved by introducing a version range (columns cdo_version_added and + * cdo_version_removed) which records for which revisions a particular entry existed. Also, this mapping is mainly + * optimized for potentially very large lists: the need for having the complete list stored in memopy to do + * in-the-middle-moved and inserts is traded in for a few more DB access operations. + * + * @author Eike Stepper + * @author Stefan Winkler + * @author Lothar Werzinger + */ +public class AuditListTableMappingWithRanges extends BasicAbstractListTableMapping implements IListMappingDeltaSupport +{ + private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, AuditListTableMappingWithRanges.class); + + /** + * Used to clean up lists for detached objects. + */ + private static final int FINAL_VERSION = Integer.MAX_VALUE; + + /** + * The table of this mapping. + */ + private IDBTable table; + + /** + * The type mapping for the value field. + */ + private ITypeMapping typeMapping; + + // --------- SQL strings - see initSqlStrings() ----------------- + private String sqlSelectChunksPrefix; + + private String sqlOrderByIndex; + + private String sqlInsertEntry; + + private String sqlDeleteEntry; + + private String sqlRemoveEntry; + + private String sqlUpdateIndex; + + private String sqlGetValue; + + private String sqlGetListLastIndex; + + private String sqlClearList; + + private String sqlDeleteList; + + public AuditListTableMappingWithRanges(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature) + { + super(mappingStrategy, eClass, feature); + initTable(); + initSqlStrings(); + } + + private void initTable() + { + String tableName = getMappingStrategy().getTableName(getContainingClass(), getFeature()); + table = getMappingStrategy().getStore().getDBSchema().addTable(tableName); + + IDBField[] dbFields = new IDBField[4]; + + dbFields[0] = table.addField(CDODBSchema.LIST_REVISION_ID, DBType.BIGINT); + dbFields[1] = table.addField(CDODBSchema.LIST_REVISION_VERSION_ADDED, DBType.INTEGER); + dbFields[2] = table.addField(CDODBSchema.LIST_REVISION_VERSION_REMOVED, DBType.INTEGER); + dbFields[3] = table.addField(CDODBSchema.LIST_IDX, DBType.INTEGER); + + // add field for value + typeMapping = getMappingStrategy().createValueMapping(getFeature()); + typeMapping.createDBField(table, CDODBSchema.LIST_VALUE); + + // TODO think about indexes + // add table indexes + table.addIndex(Type.UNIQUE, dbFields); + } + + public Collection getDBTables() + { + return Arrays.asList(table); + } + + private void initSqlStrings() + { + String tableName = getTable().getName(); + + // ---------------- read chunks ---------------------------- + StringBuilder builder = new StringBuilder(); + builder.append("SELECT "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_VALUE); + builder.append(" FROM "); //$NON-NLS-1$ + builder.append(tableName); + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED); + builder.append(" <= ? AND ( "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" IS NULL OR "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" > ? )"); //$NON-NLS-1$ + sqlSelectChunksPrefix = builder.toString(); + + sqlOrderByIndex = " ORDER BY " + CDODBSchema.LIST_IDX; //$NON-NLS-1$ + + // ----------------- count list size -------------------------- + builder = new StringBuilder("SELECT count(1) FROM "); //$NON-NLS-1$ + builder.append(tableName); + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED); + builder.append(" <= ? AND ( "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" IS NULL OR "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" > ? )"); //$NON-NLS-1$ + sqlGetListLastIndex = builder.toString(); + + // ----------------- insert entry ----------------- + builder = new StringBuilder("INSERT INTO "); //$NON-NLS-1$ + builder.append(tableName); + builder.append("("); + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(","); + builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED); + builder.append(","); + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(","); + builder.append(CDODBSchema.LIST_IDX); + builder.append(","); + builder.append(CDODBSchema.LIST_VALUE); + builder.append(") VALUES (?, ?, NULL, ?, ?)"); //$NON-NLS-1$ + sqlInsertEntry = builder.toString(); + + // ----------------- remove current entry ----------------- + builder = new StringBuilder("UPDATE "); //$NON-NLS-1$ + builder.append(getTable().getName()); + builder.append(" SET "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" = ? "); //$NON-NLS-1$ + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_IDX); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" IS NULL"); //$NON-NLS-1$ + sqlRemoveEntry = builder.toString(); + + // ----------------- delete temporary entry ----------------- + builder = new StringBuilder("DELETE FROM "); //$NON-NLS-1$ + builder.append(getTable().getName()); + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_IDX); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED); + builder.append(" = ?"); //$NON-NLS-1$ + sqlDeleteEntry = builder.toString(); + + // ----------------- update index ----------------- + builder = new StringBuilder("UPDATE "); //$NON-NLS-1$ + builder.append(getTable().getName()); + builder.append(" SET "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_IDX); + builder.append(" = ? WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_IDX); + builder.append(" = ?"); //$NON-NLS-1$ + sqlUpdateIndex = builder.toString(); + + // ----------------- get current value ----------------- + builder = new StringBuilder("SELECT "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_VALUE); + builder.append(" FROM "); //$NON-NLS-1$ + builder.append(getTable().getName()); + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_IDX); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" IS NULL"); //$NON-NLS-1$ + sqlGetValue = builder.toString(); + + // ----------- clear list items ------------------------- + builder = new StringBuilder("UPDATE "); //$NON-NLS-1$ + builder.append(getTable().getName()); + builder.append(" SET "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" = ? "); //$NON-NLS-1$ + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" IS NULL"); //$NON-NLS-1$ + sqlClearList = builder.toString(); + + // ----------- delete temporary list items ------------------------- + builder = new StringBuilder("DELETE FROM "); //$NON-NLS-1$ + builder.append(getTable().getName()); + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED); + builder.append(" = ? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED); + builder.append(" IS NULL"); //$NON-NLS-1$ + sqlDeleteList = builder.toString(); + } + + protected final IDBTable getTable() + { + return table; + } + + protected final ITypeMapping getTypeMapping() + { + return typeMapping; + } + + public void readValues(IDBStoreAccessor accessor, InternalCDORevision revision, int listChunk) + { + MoveableList list = revision.getList(getFeature()); + int listSize = -1; + + if (listChunk != CDORevision.UNCHUNKED) + { + listSize = getListLastIndex(accessor, revision.getID(), revision.getVersion()); + if (listSize == -1) + { + // list is empty - take shortcut + return; + } + + // subtract amount of items we are going to read now + listSize -= listChunk; + } + + if (TRACER.isEnabled()) + { + TRACER.format("Reading list values for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), //$NON-NLS-1$ + getFeature().getName(), revision.getID(), revision.getVersion()); + } + + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + ResultSet resultSet = null; + + try + { + String sql = sqlSelectChunksPrefix + sqlOrderByIndex; + pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.HIGH); + pstmt.setLong(1, CDOIDUtil.getLong(revision.getID())); + pstmt.setInt(2, revision.getVersion()); + pstmt.setInt(3, revision.getVersion()); + + if (listChunk != CDORevision.UNCHUNKED) + { + pstmt.setMaxRows(listChunk); // optimization - don't read unneeded rows. + } + + resultSet = pstmt.executeQuery(); + while ((listChunk == CDORevision.UNCHUNKED || --listChunk >= 0) && resultSet.next()) + { + Object value = typeMapping.readValue(resultSet); + if (TRACER.isEnabled()) + { + TRACER.format("Read value for index {0} from result set: {1}", list.size(), value); //$NON-NLS-1$ + } + + list.add(value); + } + + while (listSize-- >= 0) + { + if (TRACER.isEnabled()) + { + TRACER.format("Adding UNINITIALIZED for index {0} ", list.size()); //$NON-NLS-1$ + } + + list.add(InternalCDOList.UNINITIALIZED); + } + } + catch (SQLException ex) + { + throw new DBException(ex); + } + finally + { + DBUtil.close(resultSet); + statementCache.releasePreparedStatement(pstmt); + } + + if (TRACER.isEnabled()) + { + TRACER.format("Reading {4} list values done for feature {0}.{1} of {2}v{3}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), revision.getID(), revision.getVersion(), list.size()); + } + } + + /** + * Return the last (maximum) list index. (equals to size-1) + * + * @param accessor + * the accessor to use + * @param id + * the CDOID of the revision to which the getFeature() list belongs + * @param version + * the revision to which the getFeature() list belongs + * @return the last index or -1 if the list is empty. + */ + private int getListLastIndex(IDBStoreAccessor accessor, CDOID id, int version) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + ResultSet resultSet = null; + + try + { + pstmt = statementCache.getPreparedStatement(sqlGetListLastIndex, ReuseProbability.HIGH); + pstmt.setLong(1, CDOIDUtil.getLong(id)); + pstmt.setInt(2, version); + pstmt.setInt(3, version); + + resultSet = pstmt.executeQuery(); + if (!resultSet.next()) + { + throw new DBException("count expects exactly one result."); + } + + int result = resultSet.getInt(1) - 1; + if (TRACER.isEnabled()) + { + TRACER.trace("Read list last index = " + result); //$NON-NLS-1$ + } + + return result; + } + catch (SQLException ex) + { + throw new DBException(ex); + } + finally + { + DBUtil.close(resultSet); + statementCache.releasePreparedStatement(pstmt); + } + } + + public final void readChunks(IDBStoreChunkReader chunkReader, List chunks, String where) + { + if (TRACER.isEnabled()) + { + TRACER.format("Reading list chunk values for feature() {0}.{1} of {2}v{3}", getContainingClass().getName(), //$NON-NLS-1$ + getFeature().getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); + } + + IPreparedStatementCache statementCache = chunkReader.getAccessor().getStatementCache(); + PreparedStatement pstmt = null; + ResultSet resultSet = null; + + try + { + StringBuilder builder = new StringBuilder(sqlSelectChunksPrefix); + if (where != null) + { + builder.append(" AND "); //$NON-NLS-1$ + builder.append(where); + } + + builder.append(sqlOrderByIndex); + + String sql = builder.toString(); + pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.LOW); + pstmt.setLong(1, CDOIDUtil.getLong(chunkReader.getRevision().getID())); + pstmt.setInt(2, chunkReader.getRevision().getVersion()); + pstmt.setInt(3, chunkReader.getRevision().getVersion()); + + resultSet = pstmt.executeQuery(); + + Chunk chunk = null; + int chunkSize = 0; + int chunkIndex = 0; + int indexInChunk = 0; + + while (resultSet.next()) + { + Object value = typeMapping.readValue(resultSet); + + if (chunk == null) + { + chunk = chunks.get(chunkIndex++); + chunkSize = chunk.size(); + + if (TRACER.isEnabled()) + { + TRACER.format("Current chunk no. {0} is [start = {1}, size = {2}]", chunkIndex - 1, chunk.getStartIndex(), //$NON-NLS-1$ + chunkSize); + } + } + + if (TRACER.isEnabled()) + { + TRACER.format("Read value for chunk index {0} from result set: {1}", indexInChunk, value); //$NON-NLS-1$ + } + + chunk.add(indexInChunk++, value); + if (indexInChunk == chunkSize) + { + if (TRACER.isEnabled()) + { + TRACER.format("Chunk finished."); //$NON-NLS-1$ + } + + chunk = null; + indexInChunk = 0; + } + } + + if (TRACER.isEnabled()) + { + TRACER.format("Reading list chunk values done for feature() {0}.{1} of {2}v{3}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), chunkReader.getRevision().getID(), chunkReader + .getRevision().getVersion()); + } + } + catch (SQLException ex) + { + throw new DBException(ex); + } + finally + { + DBUtil.close(resultSet); + statementCache.releasePreparedStatement(pstmt); + } + } + + public void writeValues(IDBStoreAccessor accessor, InternalCDORevision revision) + { + CDOList values = revision.getList(getFeature()); + + int idx = 0; + for (Object element : values) + { + writeValue(accessor, revision, idx++, element); + } + + if (TRACER.isEnabled()) + { + TRACER.format("Writing done"); + } + } + + protected final void writeValue(IDBStoreAccessor accessor, CDORevision revision, int index, Object value) + { + if (TRACER.isEnabled()) + { + TRACER + .format( + "Writing value for feature {0}.{1} index {2} of {3}v{4} : {5}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), index, revision.getID(), revision.getVersion(), + value); + } + + addEntry(accessor, revision.getID(), revision.getVersion(), index, value); + } + + /** + * Clear a list of a given revision. + * + * @param accessor + * the accessor to use + * @param id + * the id of the revision from which to remove all items + */ + public void clearList(IDBStoreAccessor accessor, CDOID id, int oldVersion, int newVersion) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmtDeleteTemp = null; + PreparedStatement pstmtClear = null; + + try + { + // delete temporary entries + pstmtDeleteTemp = statementCache.getPreparedStatement(sqlDeleteList, ReuseProbability.HIGH); + pstmtDeleteTemp.setLong(1, CDOIDUtil.getLong(id)); + pstmtDeleteTemp.setInt(2, newVersion); + + int result = CDODBUtil.sqlUpdate(pstmtDeleteTemp, false); + if (TRACER.isEnabled()) + { + TRACER.format("DeleteList result: {0}", result); //$NON-NLS-1$ + } + + // clear rest of the list + pstmtClear = statementCache.getPreparedStatement(sqlClearList, ReuseProbability.HIGH); + pstmtClear.setInt(1, newVersion); + pstmtClear.setLong(2, CDOIDUtil.getLong(id)); + + result = CDODBUtil.sqlUpdate(pstmtClear, false); + if (TRACER.isEnabled()) + { + TRACER.format("ClearList result: {0}", result); //$NON-NLS-1$ + } + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmtDeleteTemp); + statementCache.releasePreparedStatement(pstmtClear); + } + } + + public void objectRevised(IDBStoreAccessor accessor, CDOID id, long revised) + { + if (TRACER.isEnabled()) + { + TRACER.format("objectRevised {0} {1}", id, revised); + } + + CDOBranch main = getMappingStrategy().getStore().getRepository().getBranchManager().getMainBranch(); + + // get revision from cache to find out version number + CDORevision revision = getMappingStrategy().getStore().getRepository().getRevisionManager().getRevision(id, + main.getHead(), /* chunksize = */0, CDORevision.DEPTH_NONE, true); + + // set cdo_revision_removed for all list items (so we have no NULL values) + clearList(accessor, id, revision.getVersion(), FINAL_VERSION); + } + + public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int oldVersion, final int newVersion, + long created, CDOListFeatureDelta delta) + { + IRepository repo = accessor.getStore().getRepository(); + InternalCDORevision originalRevision = (InternalCDORevision)repo.getRevisionManager().getRevision(id, + repo.getBranchManager().getMainBranch().getHead(), /* chunksize = */0, CDORevision.DEPTH_NONE, true); + + int oldListSize = originalRevision.getList(getFeature()).size(); + + if (TRACER.isEnabled()) + { + TRACER.format("ListTableMapping.processDelta for revision {0} - previous list size: {1}", originalRevision, + oldListSize); + } + + // let the visitor collect the changes + ListDeltaVisitor visitor = new ListDeltaVisitor(accessor, originalRevision, oldVersion, newVersion); + + if (TRACER.isEnabled()) + { + TRACER.format("processing deltas ..."); + } + + for (CDOFeatureDelta listDelta : delta.getListChanges()) + { + listDelta.accept(visitor); + } + } + + /** + * @author Stefan Winkler + */ + private class ListDeltaVisitor implements CDOFeatureDeltaVisitor + { + private IDBStoreAccessor accessor; + + private CDOID id; + + private int oldVersion; + + private int newVersion; + + private int lastIndex; + + public ListDeltaVisitor(IDBStoreAccessor accessor, InternalCDORevision originalRevision, int oldVersion, + int newVersion) + { + this.accessor = accessor; + id = originalRevision.getID(); + this.oldVersion = oldVersion; + this.newVersion = newVersion; + lastIndex = originalRevision.getList(getFeature()).size() - 1; + } + + public void visit(CDOMoveFeatureDelta delta) + { + int fromIdx = delta.getOldPosition(); + int toIdx = delta.getNewPosition(); + + if (TRACER.isEnabled()) + { + TRACER.format("Delta Moving: {0} to {1}", fromIdx, toIdx); //$NON-NLS-1$ + } + + Object value = getValue(accessor, id, fromIdx); + + // remove the item + removeEntry(accessor, id, oldVersion, newVersion, fromIdx); + + // adjust indexes and shift either up or down + if (fromIdx < toIdx) + { + moveOneUp(accessor, id, oldVersion, newVersion, fromIdx + 1, toIdx); + } + else + { // fromIdx > toIdx here + moveOneDown(accessor, id, oldVersion, newVersion, toIdx, fromIdx - 1); + } + + // create the item + addEntry(accessor, id, newVersion, toIdx, value); + } + + public void visit(CDOAddFeatureDelta delta) + { + int startIndex = delta.getIndex(); + int endIndex = lastIndex; + + if (TRACER.isEnabled()) + { + TRACER.format("Delta Adding at: {0}", startIndex); //$NON-NLS-1$ + } + + if (startIndex <= endIndex) + { + // make room for the new item + moveOneDown(accessor, id, oldVersion, newVersion, startIndex, endIndex); + } + + // create the item + addEntry(accessor, id, newVersion, startIndex, delta.getValue()); + + ++lastIndex; + } + + public void visit(CDORemoveFeatureDelta delta) + { + int startIndex = delta.getIndex(); + int endIndex = lastIndex; + + if (TRACER.isEnabled()) + { + TRACER.format("Delta Removing at: {0}", startIndex); //$NON-NLS-1$ + } + + // remove the item + removeEntry(accessor, id, oldVersion, newVersion, startIndex); + + // make room for the new item + moveOneUp(accessor, id, oldVersion, newVersion, startIndex + 1, endIndex); + + --lastIndex; + } + + public void visit(CDOSetFeatureDelta delta) + { + int index = delta.getIndex(); + + if (TRACER.isEnabled()) + { + TRACER.format("Delta Setting at: {0}", index); //$NON-NLS-1$ + } + + // remove the item + removeEntry(accessor, id, oldVersion, newVersion, index); + + // create the item + addEntry(accessor, id, newVersion, index, delta.getValue()); + } + + public void visit(CDOUnsetFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + public void visit(CDOListFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + public void visit(CDOClearFeatureDelta delta) + { + if (TRACER.isEnabled()) + { + TRACER.format("Delta Clearing"); //$NON-NLS-1$ + } + + clearList(accessor, id, oldVersion, newVersion); + lastIndex = -1; + } + + public void visit(CDOContainerFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + private void moveOneUp(IDBStoreAccessor accessor, CDOID id, int oldVersion, int newVersion, int startIndex, + int endIndex) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + + try + { + pstmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH); + + for (int index = startIndex; index <= endIndex; ++index) + { + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp moving: {0} -> {1}", index, index - 1); //$NON-NLS-1$ + } + + int stmtIndex = 1; + pstmt.setInt(stmtIndex++, index - 1); + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, newVersion); + pstmt.setInt(stmtIndex++, index); + + int result = CDODBUtil.sqlUpdate(pstmt, false); + switch (result) + { + case 0: + Object value = getValue(accessor, id, index); + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp remove: {0}", index); //$NON-NLS-1$ + } + + removeEntry(accessor, id, oldVersion, newVersion, index); + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp add: {0}", index - 1); //$NON-NLS-1$ + } + + addEntry(accessor, id, newVersion, index - 1, value); + break; + + case 1: + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp updated: {0} -> {1}", index, index - 1); //$NON-NLS-1$ + } + + break; + + default: + if (TRACER.isEnabled()) + { + TRACER.format("moveOneUp Too many results: {0} -> {1}: {2}", index, index + 1, result); //$NON-NLS-1$ + } + + throw new DBException("Too many results"); //$NON-NLS-1$ + } + } + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + } + + private void moveOneDown(IDBStoreAccessor accessor, CDOID id, int oldVersion, int newVersion, int startIndex, + int endIndex) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + + try + { + pstmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH); + + for (int index = endIndex; index >= startIndex; --index) + { + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown moving: {0} -> {1}", index, index + 1); //$NON-NLS-1$ + } + + int stmtIndex = 1; + pstmt.setInt(stmtIndex++, index + 1); + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, newVersion); + pstmt.setInt(stmtIndex++, index); + + int result = CDODBUtil.sqlUpdate(pstmt, false); + switch (result) + { + case 0: + Object value = getValue(accessor, id, index); + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown remove: {0}", index); //$NON-NLS-1$ + } + + removeEntry(accessor, id, oldVersion, newVersion, index); + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown add: {0}", index + 1); //$NON-NLS-1$ + } + + addEntry(accessor, id, newVersion, index + 1, value); + break; + + case 1: + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown updated: {0} -> {1}", index, index + 1); //$NON-NLS-1$ + } + + break; + + default: + if (TRACER.isEnabled()) + { + TRACER.format("moveOneDown Too many results: {0} -> {1}: {2}", index, index + 1, result); //$NON-NLS-1$ + } + + throw new DBException("Too many results"); //$NON-NLS-1$ + } + } + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + } + } + + private void addEntry(IDBStoreAccessor accessor, CDOID id, int version, int index, Object value) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + + if (TRACER.isEnabled()) + { + TRACER.format("Adding value for feature() {0}.{1} index {2} of {3}v{4} : {5}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), index, id, version, value); + } + + try + { + pstmt = statementCache.getPreparedStatement(sqlInsertEntry, ReuseProbability.HIGH); + + int stmtIndex = 1; + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, version); + pstmt.setInt(stmtIndex++, index); + typeMapping.setValue(pstmt, stmtIndex++, value); + + CDODBUtil.sqlUpdate(pstmt, true); + } + catch (SQLException e) + { + throw new DBException(e); + } + catch (IllegalStateException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + } + + private void removeEntry(IDBStoreAccessor accessor, CDOID id, int oldVersion, int newVersion, int index) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + + if (TRACER.isEnabled()) + { + TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), index, id, newVersion); + } + + try + { + // try to delete a temporary entry first + pstmt = statementCache.getPreparedStatement(sqlDeleteEntry, ReuseProbability.HIGH); + + int stmtIndex = 1; + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, index); + pstmt.setInt(stmtIndex++, newVersion); + + int result = CDODBUtil.sqlUpdate(pstmt, false); + if (result == 1) + { + if (TRACER.isEnabled()) + { + TRACER.format("removeEntry deleted: {0}", index); //$NON-NLS-1$ + } + } + else if (result > 1) + { + if (TRACER.isEnabled()) + { + TRACER.format("removeEntry Too many results: {0}: {1}", index, result); //$NON-NLS-1$ + } + + throw new DBException("Too many results"); //$NON-NLS-1$ + } + else + { + // no temporary entry found, so mark the entry as removed + statementCache.releasePreparedStatement(pstmt); + pstmt = statementCache.getPreparedStatement(sqlRemoveEntry, ReuseProbability.HIGH); + + stmtIndex = 1; + pstmt.setInt(stmtIndex++, newVersion); + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, index); + + CDODBUtil.sqlUpdate(pstmt, true); + } + } + catch (SQLException e) + { + if (TRACER.isEnabled()) + { + TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4} FAILED {5}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), index, id, newVersion, e.getMessage()); + } + + throw new DBException(e); + } + catch (IllegalStateException e) + { + if (TRACER.isEnabled()) + { + TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4} FAILED {5}", //$NON-NLS-1$ + getContainingClass().getName(), getFeature().getName(), index, id, newVersion, e.getMessage()); + } + + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + } + + private Object getValue(IDBStoreAccessor accessor, CDOID id, int index) + { + IPreparedStatementCache statementCache = accessor.getStatementCache(); + PreparedStatement pstmt = null; + Object result = null; + + try + { + pstmt = statementCache.getPreparedStatement(sqlGetValue, ReuseProbability.HIGH); + + int stmtIndex = 1; + pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id)); + pstmt.setInt(stmtIndex++, index); + + ResultSet resultSet = pstmt.executeQuery(); + if (!resultSet.next()) + { + throw new DBException("getValue expects exactly one result."); + } + + result = typeMapping.readValue(resultSet); + if (TRACER.isEnabled()) + { + TRACER.format("Read value (index {0}) from result set: {1}", index, result); //$NON-NLS-1$ + } + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + statementCache.releasePreparedStatement(pstmt); + } + + return result; + } +} diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BasicAbstractListTableMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BasicAbstractListTableMapping.java new file mode 100644 index 0000000000..10ecb36a61 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BasicAbstractListTableMapping.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2004 - 2009 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: + * Stefan Winkler - initial API and implementation + */ +package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal; + +import org.eclipse.emf.cdo.server.db.mapping.IListMapping; +import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EStructuralFeature; + +/** + * @author Stefan Winkler + */ +public abstract class BasicAbstractListTableMapping implements IListMapping +{ + private IMappingStrategy mappingStrategy; + + private EClass containingClass; + + private EStructuralFeature feature; + + public BasicAbstractListTableMapping(IMappingStrategy mappingStrategy, EClass containingClass, + EStructuralFeature feature) + { + this.mappingStrategy = mappingStrategy; + this.containingClass = containingClass; + this.feature = feature; + } + + public final IMappingStrategy getMappingStrategy() + { + return mappingStrategy; + } + + public final EClass getContainingClass() + { + return containingClass; + } + + public final EStructuralFeature getFeature() + { + return feature; + } +} diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditClassMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditClassMapping.java index 6bea68f0db..e2244cfb7b 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditClassMapping.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditClassMapping.java @@ -8,7 +8,8 @@ * Contributors: * Eike Stepper - initial API and implementation * Stefan Winkler - major refactoring - * Stefan Winkler - 249610: [DB] Support external references (Implementation) + * Stefan Winkler - Bug 249610: [DB] Support external references (Implementation) + * Lothar Werzinger - Bug 296440: [DB] Change RDB schema to improve scalability of to-many references in audit mode */ package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal; @@ -16,19 +17,34 @@ import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.id.CDOIDUtil; import org.eclipse.emf.cdo.common.revision.CDORevision; +import org.eclipse.emf.cdo.common.revision.delta.CDOAddFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOContainerFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDeltaVisitor; +import org.eclipse.emf.cdo.common.revision.delta.CDOListFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOMoveFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta; import org.eclipse.emf.cdo.eresource.EresourcePackage; import org.eclipse.emf.cdo.server.db.CDODBUtil; import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability; import org.eclipse.emf.cdo.server.db.mapping.IClassMapping; import org.eclipse.emf.cdo.server.db.mapping.IClassMappingAuditSupport; +import org.eclipse.emf.cdo.server.db.mapping.IClassMappingDeltaSupport; +import org.eclipse.emf.cdo.server.db.mapping.IListMappingDeltaSupport; import org.eclipse.emf.cdo.server.db.mapping.ITypeMapping; import org.eclipse.emf.cdo.server.internal.db.CDODBSchema; import org.eclipse.emf.cdo.server.internal.db.bundle.OM; +import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta; import org.eclipse.net4j.db.DBException; import org.eclipse.net4j.util.ImplementationError; +import org.eclipse.net4j.util.om.monitor.OMMonitor; +import org.eclipse.net4j.util.om.monitor.OMMonitor.Async; import org.eclipse.net4j.util.om.trace.ContextTracer; import org.eclipse.emf.ecore.EClass; @@ -43,7 +59,7 @@ import java.util.Map; * @since 2.0 */ public class HorizontalAuditClassMapping extends AbstractHorizontalClassMapping implements IClassMapping, - IClassMappingAuditSupport + IClassMappingAuditSupport, IClassMappingDeltaSupport { private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, HorizontalAuditClassMapping.class); @@ -59,6 +75,15 @@ public class HorizontalAuditClassMapping extends AbstractHorizontalClassMapping private String sqlReviseAttributes; + private ThreadLocal deltaWriter = new ThreadLocal() + { + @Override + protected FeatureDeltaWriter initialValue() + { + return new FeatureDeltaWriter(); + }; + }; + public HorizontalAuditClassMapping(AbstractHorizontalMappingStrategy mappingStrategy, EClass eClass) { super(mappingStrategy, eClass); @@ -451,4 +476,110 @@ public class HorizontalAuditClassMapping extends AbstractHorizontalClassMapping accessor.getStatementCache().releasePreparedStatement(stmt); } } + + public void writeRevisionDelta(IDBStoreAccessor accessor, InternalCDORevisionDelta delta, long created, + OMMonitor monitor) + { + monitor.begin(); + Async async = monitor.forkAsync(); + + try + { + FeatureDeltaWriter writer = deltaWriter.get(); + writer.process(accessor, delta, created); + } + finally + { + async.stop(); + monitor.done(); + } + } + + /** + * @author Stefan Winkler + */ + private class FeatureDeltaWriter implements CDOFeatureDeltaVisitor + { + private IDBStoreAccessor accessor; + + private long created; + + private CDOID id; + + private int oldVersion; + + private int newVersion; + + private InternalCDORevision newRevision; + + public void process(IDBStoreAccessor accessor, InternalCDORevisionDelta delta, long created) + { + this.accessor = accessor; + this.created = created; + id = delta.getID(); + oldVersion = delta.getVersion(); + + if (TRACER.isEnabled()) + { + TRACER.format("FeatureDeltaWriter: old version: {0}, new version: {1}", oldVersion, newVersion); //$NON-NLS-1$ + } + + InternalCDORevision originalRevision = (InternalCDORevision)accessor.getStore().getRepository() + .getRevisionManager().getRevisionByVersion(id, delta, 0, true); + + newRevision = (InternalCDORevision)originalRevision.copy(); + + newRevision.setVersion(newVersion); + newRevision.setBranchPoint(CDOBranchUtil.createBranchPoint(delta.getBranch(), created)); + + // process revision delta tree + delta.accept(this); + + long revised = newRevision.getTimeStamp() - 1; + reviseObject(accessor, id, revised); + + writeValues(accessor, newRevision); + } + + public void visit(CDOMoveFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + public void visit(CDOAddFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + public void visit(CDORemoveFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + public void visit(CDOSetFeatureDelta delta) + { + delta.apply(newRevision); + } + + public void visit(CDOUnsetFeatureDelta delta) + { + delta.apply(newRevision); + } + + public void visit(CDOListFeatureDelta delta) + { + IListMappingDeltaSupport listMapping = (IListMappingDeltaSupport)getListMapping(delta.getFeature()); + listMapping.processDelta(accessor, id, oldVersion, newVersion, created, delta); + } + + public void visit(CDOClearFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + public void visit(CDOContainerFeatureDelta delta) + { + delta.apply(newRevision); + } + } } diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditMappingStrategyWithRanges.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditMappingStrategyWithRanges.java new file mode 100644 index 0000000000..eeae640fb1 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditMappingStrategyWithRanges.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2004 - 2009 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 - major refactoring + */ +package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal; + +import org.eclipse.emf.cdo.server.db.mapping.IClassMapping; +import org.eclipse.emf.cdo.server.db.mapping.IListMapping; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EStructuralFeature; + +/** + * @author Eike Stepper + * @since 2.0 + */ +public class HorizontalAuditMappingStrategyWithRanges extends AbstractHorizontalMappingStrategy +{ + public HorizontalAuditMappingStrategyWithRanges() + { + } + + @Override + public boolean hasDeltaSupport() + { + return true; + } + + @Override + public boolean hasAuditSupport() + { + return true; + } + + public boolean hasBranchingSupport() + { + return false; + } + + @Override + public IClassMapping doCreateClassMapping(EClass eClass) + { + return new HorizontalAuditClassMapping(this, eClass); + } + + @Override + public IListMapping doCreateListMapping(EClass containingClass, EStructuralFeature feature) + { + return new AuditListTableMappingWithRanges(this, containingClass, feature); + } + + @Override + public IListMapping doCreateFeatureMapMapping(EClass containingClass, EStructuralFeature feature) + { + return new AuditFeatureMapTableMappingWithRanges(this, containingClass, feature); + } +} diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContextImpl.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContextImpl.java index 1f27e82c37..c633868d53 100644 --- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContextImpl.java +++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContextImpl.java @@ -253,18 +253,18 @@ public class TransactionCommitContextImpl implements InternalCommitContext { monitor.begin(106); + adjustMetaRanges(); + monitor.worked(); + + lockObjects(); + // Could throw an exception timeStamp = createTimeStamp(); dirtyObjects = new InternalCDORevision[dirtyObjectDeltas.length]; - adjustMetaRanges(); - monitor.worked(); - adjustTimeStamps(); monitor.worked(); - lockObjects(); - InternalRepository repository = transaction.getRepository(); computeDirtyObjects(!repository.isSupportingRevisionDeltas(), monitor.fork()); diff --git a/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2.java b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2.java index 8b17c241fc..0ad495418e 100644 --- a/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2.java +++ b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2.java @@ -7,10 +7,10 @@ * * Contributors: * Eike Stepper - initial API and implementation + * Stefan Winkler - introduced variable mapping strategies */ package org.eclipse.emf.cdo.tests.db; -import org.eclipse.emf.cdo.server.db.CDODBUtil; import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy; import org.eclipse.net4j.db.DBUtil; @@ -45,7 +45,8 @@ public class AllTestsDBH2 extends DBConfigs @Override protected void initConfigSuites(TestSuite parent) { - addScenario(parent, COMBINED, AllTestsDBH2.H2.ReusableFolder.INSTANCE, JVM, NATIVE); + addScenario(parent, COMBINED, AllTestsDBH2.H2.ReusableFolder.AUDIT_INSTANCE, JVM, NATIVE); + addScenario(parent, COMBINED, AllTestsDBH2.H2.ReusableFolder.RANGE_INSTANCE, JVM, NATIVE); } /** @@ -55,19 +56,32 @@ public class AllTestsDBH2 extends DBConfigs { private static final long serialVersionUID = 1L; - public static final AllTestsDBH2.H2 INSTANCE = new H2("DBStore: H2"); + public static final AllTestsDBH2.H2 INSTANCE = new H2("DBStore: H2 (audit)", + "org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalAuditMappingStrategy"); protected transient File dbFolder; - public H2(String name) + private String mappingStrategy; + + public H2(String name, String mappingStrategy) { super(name); + this.mappingStrategy = mappingStrategy; } + @SuppressWarnings("unchecked") @Override protected IMappingStrategy createMappingStrategy() { - return CDODBUtil.createHorizontalMappingStrategy(true); + try + { + Class clazz = (Class)Class.forName(mappingStrategy); + return clazz.newInstance(); + } + catch (Exception ex) + { + throw WrappedException.wrap(ex); + } } @Override @@ -107,7 +121,12 @@ public class AllTestsDBH2 extends DBConfigs { private static final long serialVersionUID = 1L; - public static final ReusableFolder INSTANCE = new ReusableFolder("DBStore: H2 (Reusable Folder)"); + public static final ReusableFolder AUDIT_INSTANCE = new ReusableFolder("DBStore: H2 (Reusable Folder, audit), ", + "org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalAuditMappingStrategy"); + + public static final ReusableFolder RANGE_INSTANCE = new ReusableFolder( + "DBStore: H2 (Reusable Folder, audit, range-based mapping strategy)", + "org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalAuditMappingStrategyWithRanges"); private static File reusableFolder; @@ -115,9 +134,9 @@ public class AllTestsDBH2 extends DBConfigs private transient ArrayList repoNames = new ArrayList(); - public ReusableFolder(String name) + public ReusableFolder(String name, String mappingStrategy) { - super(name); + super(name, mappingStrategy); } @Override @@ -138,6 +157,7 @@ public class AllTestsDBH2 extends DBConfigs Connection conn = null; Statement stmt = null; + try { conn = defaultDataSource.getConnection(); @@ -157,7 +177,6 @@ public class AllTestsDBH2 extends DBConfigs JdbcDataSource dataSource = new JdbcDataSource(); dataSource.setURL("jdbc:h2:" + dbFolder.getAbsolutePath() + "/h2test;SCHEMA=" + repoName); - return dataSource; } diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTestsAllConfigs.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTestsAllConfigs.java index c958582000..eba156c6ef 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTestsAllConfigs.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTestsAllConfigs.java @@ -62,6 +62,7 @@ import org.eclipse.emf.cdo.tests.bugzilla.Bugzilla_289984_Test; import org.eclipse.emf.cdo.tests.bugzilla.Bugzilla_292372_Test; import org.eclipse.emf.cdo.tests.bugzilla.Bugzilla_294850_Test; import org.eclipse.emf.cdo.tests.bugzilla.Bugzilla_294859_Test; +import org.eclipse.emf.cdo.tests.bugzilla.Bugzilla_298561_Test; import org.eclipse.emf.cdo.tests.bugzilla.Bugzilla_299190_Test; import org.eclipse.emf.cdo.tests.config.impl.ConfigTest; import org.eclipse.emf.cdo.tests.config.impl.ConfigTestSuite; @@ -181,6 +182,7 @@ public abstract class AllTestsAllConfigs extends ConfigTestSuite testClasses.add(Bugzilla_294850_Test.class); testClasses.add(Bugzilla_294859_Test.class); testClasses.add(Bugzilla_292372_Test.class); + testClasses.add(Bugzilla_298561_Test.class); testClasses.add(Bugzilla_299190_Test.class); // TODO testClasses.add(NonCDOResourceTest.class); diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_298561_Test.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_298561_Test.java new file mode 100644 index 0000000000..454ae099cb --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_298561_Test.java @@ -0,0 +1,233 @@ +package org.eclipse.emf.cdo.tests.bugzilla; + +import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.CDOState; +import org.eclipse.emf.cdo.eresource.CDOResource; +import org.eclipse.emf.cdo.session.CDOSession; +import org.eclipse.emf.cdo.tests.AbstractCDOTest; +import org.eclipse.emf.cdo.tests.model4.ContainedElementNoOpposite; +import org.eclipse.emf.cdo.tests.model4.RefMultiNonContainedNPL; +import org.eclipse.emf.cdo.tests.model4.RefSingleNonContainedNPL; +import org.eclipse.emf.cdo.tests.model4.model4Factory; +import org.eclipse.emf.cdo.tests.model4.model4Package; +import org.eclipse.emf.cdo.transaction.CDOTransaction; +import org.eclipse.emf.cdo.util.ObjectNotFoundException; + +import org.eclipse.emf.ecore.util.EcoreUtil; + +public class Bugzilla_298561_Test extends AbstractCDOTest +{ + private static String RESOURCENAME = "/r1"; + + public void test_new() + { + CDOSession session = openSession(); + session.options().setPassiveUpdateEnabled(false); + session.getPackageRegistry().putEPackage(model4Package.eINSTANCE); + + CDOTransaction tx = session.openTransaction(); + CDOResource r1 = tx.createResource(RESOURCENAME); + + // Create referencee and store it + ContainedElementNoOpposite referencee = model4Factory.eINSTANCE.createContainedElementNoOpposite(); + r1.getContents().add(referencee); + tx.commit(); + + // Create referencer, don't store it -- keep it as NEW + RefSingleNonContainedNPL referencer = model4Factory.eINSTANCE.createRefSingleNonContainedNPL(); + r1.getContents().add(referencer); + referencer.setElement(referencee); + assertEquals(CDOState.NEW, ((CDOObject)referencer).cdoState()); + + // Delete the referencee in 2nd session + doSecondSession(); + + // Refresh + session.refresh(); + + // + try + { + boolean isSet = referencer.eIsSet(model4Package.eINSTANCE.getRefSingleNonContainedNPL_Element()); + System.out.println("---> " + isSet); + + if (isSet) + { + ContainedElementNoOpposite e = referencer.getElement(); + System.out.println("---> " + e); + } + } + catch (ObjectNotFoundException e) + { + fail("Should not have thrown ObjectNotFoundException"); + } + + tx.close(); + session.close(); + } + + public void test_dirty() + { + CDOSession session = openSession(); + session.options().setPassiveUpdateEnabled(false); + session.getPackageRegistry().putEPackage(model4Package.eINSTANCE); + + CDOTransaction tx = session.openTransaction(); + CDOResource r1 = tx.createResource(RESOURCENAME); + + // Create referencee and store it + ContainedElementNoOpposite referencee = model4Factory.eINSTANCE.createContainedElementNoOpposite(); + r1.getContents().add(referencee); + tx.commit(); + + System.out.println("---> " + ((CDOObject)referencee).cdoID()); + + // Create referencer, store it, then make it DIRTY + RefSingleNonContainedNPL referencer = model4Factory.eINSTANCE.createRefSingleNonContainedNPL(); + r1.getContents().add(referencer); + referencer.setElement(referencee); + tx.commit(); + referencer.setElement(null); + referencer.setElement(referencee); + assertEquals(CDOState.DIRTY, ((CDOObject)referencer).cdoState()); + + // Delete the referencee in 2nd session + doSecondSession(); + + // Refresh + session.refresh(); + + // + try + { + boolean isSet = referencer.eIsSet(model4Package.eINSTANCE.getRefSingleNonContainedNPL_Element()); + System.out.println("---> " + isSet); + + if (isSet) + { + ContainedElementNoOpposite e = referencer.getElement(); + System.out.println("---> " + e); + } + } + catch (ObjectNotFoundException e) + { + fail("Should not have thrown ObjectNotFoundException"); + } + + tx.close(); + session.close(); + } + + private void doSecondSession() + { + CDOSession session = openModel1Session(); + CDOTransaction tx = session.openTransaction(); + CDOResource r1 = tx.getResource(RESOURCENAME); + ContainedElementNoOpposite referencee = (ContainedElementNoOpposite)r1.getContents().get(0); + EcoreUtil.delete(referencee); + tx.commit(); + tx.close(); + session.close(); + } + + public void test_new_multi() + { + CDOSession session = openSession(); + session.options().setPassiveUpdateEnabled(false); + session.getPackageRegistry().putEPackage(model4Package.eINSTANCE); + + CDOTransaction tx = session.openTransaction(); + CDOResource r1 = tx.createResource(RESOURCENAME); + + // Create referencee and store it + ContainedElementNoOpposite referencee = model4Factory.eINSTANCE.createContainedElementNoOpposite(); + r1.getContents().add(referencee); + tx.commit(); + + System.out.println("---> " + ((CDOObject)referencee).cdoID()); + + // Create referencer, don't store it -- keep it as NEW + RefMultiNonContainedNPL referencer = model4Factory.eINSTANCE.createRefMultiNonContainedNPL(); + r1.getContents().add(referencer); + referencer.getElements().add(referencee); + assertEquals(CDOState.NEW, ((CDOObject)referencer).cdoState()); + + // Delete the referencee in 2nd session + doSecondSession(); + + // Refresh + session.refresh(); + + // + try + { + boolean isSet = referencer.eIsSet(model4Package.eINSTANCE.getRefMultiNonContainedNPL_Elements()); + System.out.println("---> " + isSet); + + if (isSet && referencer.getElements().size() > 0) + { + ContainedElementNoOpposite e = referencer.getElements().get(0); + System.out.println("---> " + e); + } + } + catch (ObjectNotFoundException e) + { + fail("Should not have thrown ObjectNotFoundException"); + } + + tx.close(); + session.close(); + } + + public void test_dirty_multi() + { + CDOSession session = openSession(); + session.options().setPassiveUpdateEnabled(false); + session.getPackageRegistry().putEPackage(model4Package.eINSTANCE); + + CDOTransaction tx = session.openTransaction(); + CDOResource r1 = tx.createResource(RESOURCENAME); + + // Create referencee and store it + ContainedElementNoOpposite referencee = model4Factory.eINSTANCE.createContainedElementNoOpposite(); + r1.getContents().add(referencee); + tx.commit(); + + System.out.println("---> " + ((CDOObject)referencee).cdoID()); + + // Create referencer, store it, then make it DIRTY + RefMultiNonContainedNPL referencer = model4Factory.eINSTANCE.createRefMultiNonContainedNPL(); + r1.getContents().add(referencer); + referencer.getElements().add(referencee); + tx.commit(); + referencer.getElements().remove(referencee); + referencer.getElements().add(referencee); + assertEquals(CDOState.DIRTY, ((CDOObject)referencer).cdoState()); + + // Delete the referencee in 2nd session + doSecondSession(); + + // Refresh + session.refresh(); + + // + try + { + boolean isSet = referencer.eIsSet(model4Package.eINSTANCE.getRefMultiNonContainedNPL_Elements()); + System.out.println("---> " + isSet); + + if (isSet && referencer.getElements().size() > 0) + { + ContainedElementNoOpposite e = referencer.getElements().get(0); + System.out.println("---> " + e); + } + } + catch (ObjectNotFoundException e) + { + fail("Should not have thrown ObjectNotFoundException"); + } + + tx.close(); + session.close(); + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java index 62d35f8767..a0270a74e8 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java @@ -81,7 +81,14 @@ import org.eclipse.net4j.util.options.OptionsEvent; import org.eclipse.net4j.util.transaction.TransactionException; import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.InternalEObject; +import org.eclipse.emf.ecore.EStructuralFeature.Setting; +import org.eclipse.emf.ecore.util.EContentsEList; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.emf.ecore.util.EContentsEList.FeatureIterator; import org.eclipse.emf.spi.cdo.CDOTransactionStrategy; import org.eclipse.emf.spi.cdo.InternalCDOObject; import org.eclipse.emf.spi.cdo.InternalCDOSavepoint; @@ -1575,6 +1582,41 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa super.doDeactivate(); } + /** + * @since 3.0 + */ + @Override + public Set handleInvalidationWithoutNotification(Set dirtyOIDs, + Collection detachedOIDs, Set dirtyObjects, Set detachedObjects) + { + // Bugzilla 298561: This override removes references to remotely + // detached objects that are present in any DIRTY or NEW objects + + removeCrossReferences(getDirtyObjects().values(), detachedOIDs); + removeCrossReferences(getNewObjects().values(), detachedOIDs); + + return super.handleInvalidationWithoutNotification(dirtyOIDs, detachedOIDs, dirtyObjects, detachedObjects); + } + + private void removeCrossReferences(Collection objects, Collection referencedOIDs) + { + for (CDOObject object : objects) + { + for (EContentsEList.FeatureIterator it = (FeatureIterator)object.eCrossReferences().iterator(); it + .hasNext();) + { + EObject crossReferencedObject = it.next(); + if (crossReferencedObject instanceof CDOObject + && referencedOIDs.contains(((CDOObject)crossReferencedObject).cdoID())) + { + EReference eReference = (EReference)it.feature(); + Setting setting = ((InternalEObject)object).eSetting(eReference); + EcoreUtil.remove(setting, crossReferencedObject); + } + } + } + } + /** * @author Simon McDuff */ -- cgit v1.2.3