Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEike Stepper2011-12-21 07:55:46 -0500
committerEike Stepper2011-12-21 07:55:46 -0500
commitc4d77b54ea1984b36ed152f69e920f9d0fade919 (patch)
tree34dbf28ee1beffecc383841e65023308dc2756f1
parentc32790075e3e94ec621825440ea746cdd56af497 (diff)
parent3c163974928f397a7d53957ba62d5e4df0c79425 (diff)
downloadcdo-c4d77b54ea1984b36ed152f69e920f9d0fade919.tar.gz
cdo-c4d77b54ea1984b36ed152f69e920f9d0fade919.tar.xz
cdo-c4d77b54ea1984b36ed152f69e920f9d0fade919.zip
Merge branch 'bugs/366686'
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/CDODBUtil.java5
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/IListMapping2.java28
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreChunkReader.java32
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BasicAbstractListTableMapping.java25
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditMappingStrategy.java17
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java1083
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/RevisionDeltaTest.java132
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/performance/DeletePerformanceTest.java29
-rw-r--r--plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/DBUtil.java14
9 files changed, 984 insertions, 381 deletions
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/CDODBUtil.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/CDODBUtil.java
index b91f504011..1fe225cb17 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/CDODBUtil.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/CDODBUtil.java
@@ -60,6 +60,11 @@ public final class CDODBUtil
*/
public static final String PROP_COPY_ON_BRANCH = "copyOnBranch";
+ /**
+ * @since 4.1
+ */
+ public static final String PROP_ZEROBASED_INDEX = "forceZeroBasedIndex";
+
private CDODBUtil()
{
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/IListMapping2.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/IListMapping2.java
new file mode 100644
index 0000000000..9dcfd6cbc9
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/IListMapping2.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2004 - 2011 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
+ */
+package org.eclipse.emf.cdo.server.db.mapping;
+
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
+
+/**
+ * Extension interface to {@link IListMapping}.
+ *
+ * @author Eike Stepper
+ * @since 4.1
+ */
+public interface IListMapping2 extends IListMapping
+{
+ public void addSimpleChunkWhere(IDBStoreAccessor accessor, CDOID cdoid, StringBuilder builder, int index);
+
+ public void addRangedChunkWhere(IDBStoreAccessor accessor, CDOID cdoid, StringBuilder builder, int fromIndex,
+ int toIndex);
+}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreChunkReader.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreChunkReader.java
index b98c95fc73..2295a59bb7 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreChunkReader.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreChunkReader.java
@@ -16,6 +16,7 @@ import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.server.db.IDBStoreChunkReader;
import org.eclipse.emf.cdo.server.db.mapping.IClassMapping;
import org.eclipse.emf.cdo.server.db.mapping.IListMapping;
+import org.eclipse.emf.cdo.server.db.mapping.IListMapping2;
import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
import org.eclipse.emf.cdo.spi.server.StoreChunkReader;
@@ -52,9 +53,16 @@ public class DBStoreChunkReader extends StoreChunkReader implements IDBStoreChun
super.addSimpleChunk(index);
prepareAddition();
- builder.append(CDODBSchema.LIST_IDX);
- builder.append('=');
- builder.append(index);
+ if (referenceMapping instanceof IListMapping2)
+ {
+ ((IListMapping2)referenceMapping).addSimpleChunkWhere(getAccessor(), getRevision().getID(), builder, index);
+ }
+ else
+ {
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append('=');
+ builder.append(index);
+ }
}
@Override
@@ -63,11 +71,19 @@ public class DBStoreChunkReader extends StoreChunkReader implements IDBStoreChun
super.addRangedChunk(fromIndex, toIndex);
prepareAddition();
- builder.append(CDODBSchema.LIST_IDX);
- builder.append(" BETWEEN "); //$NON-NLS-1$
- builder.append(fromIndex);
- builder.append(" AND "); //$NON-NLS-1$
- builder.append(toIndex - 1);
+ if (referenceMapping instanceof IListMapping2)
+ {
+ ((IListMapping2)referenceMapping).addRangedChunkWhere(getAccessor(), getRevision().getID(), builder, fromIndex,
+ toIndex);
+ }
+ else
+ {
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append(" BETWEEN "); //$NON-NLS-1$
+ builder.append(fromIndex);
+ builder.append(" AND "); //$NON-NLS-1$
+ builder.append(toIndex - 1);
+ }
}
public List<Chunk> executeRead()
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
index 9fccee2e47..f5c54477e1 100644
--- 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
@@ -10,8 +10,11 @@
*/
package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal;
-import org.eclipse.emf.cdo.server.db.mapping.IListMapping;
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
+import org.eclipse.emf.cdo.server.db.mapping.IListMapping2;
import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
+import org.eclipse.emf.cdo.server.internal.db.CDODBSchema;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
@@ -19,7 +22,7 @@ import org.eclipse.emf.ecore.EStructuralFeature;
/**
* @author Stefan Winkler
*/
-public abstract class BasicAbstractListTableMapping implements IListMapping
+public abstract class BasicAbstractListTableMapping implements IListMapping2
{
private IMappingStrategy mappingStrategy;
@@ -49,4 +52,22 @@ public abstract class BasicAbstractListTableMapping implements IListMapping
{
return feature;
}
+
+ public void addSimpleChunkWhere(IDBStoreAccessor accessor, CDOID cdoid, StringBuilder builder, int index)
+ {
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append('=');
+ builder.append(index);
+ }
+
+ public void addRangedChunkWhere(IDBStoreAccessor accessor, CDOID cdoid, StringBuilder builder, int fromIndex,
+ int toIndex)
+ {
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append(" BETWEEN "); //$NON-NLS-1$
+ builder.append(fromIndex);
+ builder.append(" AND "); //$NON-NLS-1$
+ builder.append(toIndex - 1);
+ }
+
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditMappingStrategy.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditMappingStrategy.java
index b317c16cdd..86beda1121 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditMappingStrategy.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditMappingStrategy.java
@@ -11,6 +11,7 @@
*/
package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal;
+import org.eclipse.emf.cdo.server.db.CDODBUtil;
import org.eclipse.emf.cdo.server.db.mapping.IClassMapping;
import org.eclipse.emf.cdo.server.db.mapping.IListMapping;
@@ -23,6 +24,8 @@ import org.eclipse.emf.ecore.EStructuralFeature;
*/
public class HorizontalNonAuditMappingStrategy extends AbstractHorizontalMappingStrategy
{
+ private boolean forceZeroBasedIndex;
+
public HorizontalNonAuditMappingStrategy()
{
}
@@ -42,6 +45,11 @@ public class HorizontalNonAuditMappingStrategy extends AbstractHorizontalMapping
return true;
}
+ public boolean shallForceZeroBasedIndex()
+ {
+ return forceZeroBasedIndex;
+ }
+
@Override
public IListMapping doCreateListMapping(EClass containingClass, EStructuralFeature feature)
{
@@ -59,4 +67,13 @@ public class HorizontalNonAuditMappingStrategy extends AbstractHorizontalMapping
{
return new HorizontalNonAuditClassMapping(this, eClass);
}
+
+ @Override
+ protected void doAfterActivate() throws Exception
+ {
+ super.doAfterActivate();
+
+ String value = getProperties().get(CDODBUtil.PROP_ZEROBASED_INDEX);
+ forceZeroBasedIndex = value == null ? false : Boolean.valueOf(value);
+ }
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java
index 18455f145c..5a28077f51 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java
@@ -27,6 +27,7 @@ import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta;
import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
import org.eclipse.emf.cdo.server.db.IIDHandler;
+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;
@@ -45,8 +46,12 @@ import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.core.runtime.Assert;
import java.sql.PreparedStatement;
+import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.ListIterator;
/**
* This is a list-to-table mapping optimized for non-audit-mode. It doesn't care about version and has delta support.
@@ -62,17 +67,6 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
private static final int UNBOUNDED_SHIFT = -1;
- /**
- * The central data structure which is used to calculate the outcomes of the list deltas.
- */
- private ArrayList<ManipulationElement> manipulations = new ArrayList<ManipulationElement>();
-
- /**
- * This is a flag to remember if a delta of type "clear" has been encountered. If so, the list in the DB has to be
- * cleared before writing out the changes.
- */
- private boolean clearFirst;
-
private String sqlClear;
private String sqlUpdateValue;
@@ -83,6 +77,12 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
private String sqlDeleteItem;
+ private String sqlShiftDownIndex;
+
+ private String sqlReadCurrentIndexOffset;
+
+ private String sqlShiftUpIndex;
+
public NonAuditListTableMapping(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature)
{
super(mappingStrategy, eClass, feature);
@@ -148,6 +148,54 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
builder.append(CDODBSchema.LIST_IDX);
builder.append("=? "); //$NON-NLS-1$
sqlUpdateIndex = builder.toString();
+
+ // ----------- mass update item indexes --------------
+ builder = new StringBuilder();
+ builder.append("UPDATE "); //$NON-NLS-1$
+ builder.append(getTable());
+ builder.append(" SET "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append("="); //$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_IDX);
+ builder.append(" BETWEEN ? AND ?"); //$NON-NLS-1$
+ // getMappingStrategy().getStore().getDBAdapter()
+
+ // needed because of MySQL:
+ builder.append("/*! ORDER BY "); //$NON-NLS-1$ /
+ builder.append(CDODBSchema.LIST_IDX);
+ sqlShiftDownIndex = builder.toString() + " */"; //$NON-NLS-1$
+ builder.append(" DESC"); //$NON-NLS-1$
+ sqlShiftUpIndex = builder.toString() + " */"; //$NON-NLS-1$
+
+ // ----------- read current index offset --------------
+ builder = new StringBuilder();
+ builder.append("SELECT MIN("); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append(") FROM "); //$NON-NLS-1$
+ builder.append(getTable());
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append("=?"); //$NON-NLS-1$
+ sqlReadCurrentIndexOffset = builder.toString();
+ }
+
+ @Override
+ public void addSimpleChunkWhere(IDBStoreAccessor accessor, CDOID cdoid, StringBuilder builder, int index)
+ {
+ int offset = getCurrentIndexOffset(accessor, cdoid);
+ super.addSimpleChunkWhere(accessor, cdoid, builder, index + offset);
+ }
+
+ @Override
+ public void addRangedChunkWhere(IDBStoreAccessor accessor, CDOID cdoid, StringBuilder builder, int fromIndex,
+ int toIndex)
+ {
+ int offset = getCurrentIndexOffset(accessor, cdoid);
+ super.addRangedChunkWhere(accessor, cdoid, builder, fromIndex + offset, toIndex + offset);
}
@Override
@@ -183,11 +231,12 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
*/
public void clearList(IDBStoreAccessor accessor, CDOID id)
{
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
- stmt = accessor.getStatementCache().getPreparedStatement(sqlClear, ReuseProbability.HIGH);
+ stmt = statementCache.getPreparedStatement(sqlClear, ReuseProbability.HIGH);
getMappingStrategy().getStore().getIDHandler().setCDOID(stmt, 1, id);
DBUtil.update(stmt, false);
}
@@ -197,7 +246,37 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
}
finally
{
- accessor.getStatementCache().releasePreparedStatement(stmt);
+ statementCache.releasePreparedStatement(stmt);
+ }
+ }
+
+ public int getCurrentIndexOffset(IDBStoreAccessor accessor, CDOID id)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement stmt = null;
+ ResultSet rset = null;
+
+ try
+ {
+ stmt = statementCache.getPreparedStatement(sqlReadCurrentIndexOffset, ReuseProbability.HIGH);
+ getMappingStrategy().getStore().getIDHandler().setCDOID(stmt, 1, id);
+ rset = stmt.executeQuery();
+ if (!rset.next())
+ {
+ // list is empty. Return the default offset of 0.
+ return 0;
+ }
+ // return the minimum index which is equal to the current offset.
+ return rset.getInt(1);
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ DBUtil.close(rset);
+ releaseStatement(accessor, stmt);
}
}
@@ -210,32 +289,14 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
.getRevisionManager().getRevision(id, main, CDORevision.UNCHUNKED, CDORevision.DEPTH_NONE, true);
int oldListSize = originalRevision.getList(getFeature()).size();
- // reset the clear-flag
- clearFirst = false;
-
if (TRACER.isEnabled())
{
TRACER.format("ListTableMapping.processDelta for revision {0} - previous list size: {1}", originalRevision, //$NON-NLS-1$
oldListSize);
}
- if (manipulations == null)
- {
- manipulations = new ArrayList<ManipulationElement>();
- }
- else
- {
- manipulations.clear();
- }
-
- // create list and initialize with original indexes
- for (int i = 0; i < oldListSize; i++)
- {
- manipulations.add(createOriginalElement(i));
- }
-
// let the visitor collect the changes
- ListDeltaVisitor visitor = new ListDeltaVisitor();
+ ListDeltaVisitor visitor = new ListDeltaVisitor(oldListSize);
if (TRACER.isEnabled())
{
@@ -247,271 +308,10 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
listDelta.accept(visitor);
}
- // TODO: here, we could implement further optimizations.
- // e.g., if more than 50% of the list's items are removed,
- // it's better to clear the list and reinsert all values
- // from scratch.
+ visitor.postProcess(accessor, id);
// finally, write results to the database
- writeResultToDatabase(accessor, id);
- }
-
- /**
- * Write calculated changes to the database
- *
- * @param accessor
- */
- private void writeResultToDatabase(IDBStoreAccessor accessor, CDOID id)
- {
- IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
- PreparedStatement deleteStmt = null;
- PreparedStatement moveStmt = null;
- PreparedStatement setValueStmt = null;
- PreparedStatement insertStmt = null;
-
- int deleteCounter = 0;
- int moveCounter = 0;
- int setValueCounter = 0;
- int insertCounter = 0;
-
- int tempIndex = -1;
-
- if (TRACER.isEnabled())
- {
- TRACER.trace("Writing to database:"); //$NON-NLS-1$
- }
-
- if (clearFirst)
- {
- if (TRACER.isEnabled())
- {
- TRACER.trace(" - clear list"); //$NON-NLS-1$
- }
-
- clearList(accessor, id);
- }
-
- try
- {
- for (ManipulationElement element : manipulations)
- {
- if (element.is(ManipulationConstants.DELETE))
- {
- /*
- * Step 1: DELETE all elements e which have e.is(REMOVE) by e.sourceIdx
- */
-
- if (deleteStmt == null)
- {
- deleteStmt = accessor.getStatementCache().getPreparedStatement(sqlDeleteItem, ReuseProbability.HIGH);
- idHandler.setCDOID(deleteStmt, 1, id);
- }
-
- deleteStmt.setInt(2, element.sourceIndex);
- deleteStmt.addBatch();
- deleteCounter++;
-
- if (TRACER.isEnabled())
- {
- TRACER.format(" - delete at {0} ", element.sourceIndex); //$NON-NLS-1$
- }
- }
-
- if (element.is(ManipulationConstants.MOVE))
- {
- /*
- * Step 2: MOVE all elements e (by e.sourceIdx) which have e.is(MOVE) to temporary idx (-1, -2, -3, -4, ...)
- * and store temporary idx in e.tempIndex
- */
- if (moveStmt == null)
- {
- moveStmt = accessor.getStatementCache().getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH);
- idHandler.setCDOID(moveStmt, 2, id);
- }
-
- moveStmt.setInt(3, element.sourceIndex); // from index
- moveStmt.setInt(1, --tempIndex); // to index
- element.tempIndex = tempIndex;
- moveStmt.addBatch();
- moveCounter++;
-
- if (TRACER.isEnabled())
- {
- TRACER.format(" - move {0} -> {1} ", element.sourceIndex, element.tempIndex); //$NON-NLS-1$
- }
- }
- }
-
- /*
- * Step 3: move all elements which have to be shifted up or down because of add, remove or move of other elements
- * to their proper position. This has to be done in two phases to avoid collisions, as the index has to be unique
- */
- int size = manipulations.size();
-
- /* Step 3a: shift down */
- for (int i = 0; i < size; i++)
- {
- ManipulationElement element = manipulations.get(i);
-
- if ((element.type == ManipulationConstants.NONE || element.type == ManipulationConstants.SET_VALUE)
- && element.sourceIndex > element.destinationIndex)
- {
- if (moveStmt == null)
- {
- moveStmt = accessor.getStatementCache().getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH);
- idHandler.setCDOID(moveStmt, 2, id);
- }
-
- moveStmt.setInt(3, element.sourceIndex); // from index
- moveStmt.setInt(1, element.destinationIndex); // to index
- moveStmt.addBatch();
- moveCounter++;
- if (TRACER.isEnabled())
- {
- TRACER.format(" - move {0} -> {1} ", element.sourceIndex, element.destinationIndex); //$NON-NLS-1$
- }
- }
- }
-
- /* Step 3b: shift up */
- for (int i = size - 1; i >= 0; i--)
- {
- ManipulationElement element = manipulations.get(i);
-
- if ((element.type == ManipulationConstants.NONE || element.type == ManipulationConstants.SET_VALUE)
- && element.sourceIndex < element.destinationIndex)
- {
- if (moveStmt == null)
- {
- moveStmt = accessor.getStatementCache().getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH);
- idHandler.setCDOID(moveStmt, 2, id);
- }
-
- moveStmt.setInt(3, element.sourceIndex); // from index
- moveStmt.setInt(1, element.destinationIndex); // to index
- moveStmt.addBatch();
- moveCounter++;
- if (TRACER.isEnabled())
- {
- TRACER.format(" - move {0} -> {1} ", element.sourceIndex, element.destinationIndex); //$NON-NLS-1$
- }
- }
- }
-
- for (ManipulationElement element : manipulations)
- {
- if (element.is(ManipulationConstants.MOVE))
- {
- /*
- * Step 4: MOVE all elements e have e.is(MOVE) from e.tempIdx to e.destinationIdx (because we have moved them
- * before, moveStmt is always initialized
- */
- moveStmt.setInt(3, element.tempIndex); // from index
- moveStmt.setInt(1, element.destinationIndex); // to index
- element.tempIndex = tempIndex;
- moveStmt.addBatch();
- moveCounter++;
-
- if (TRACER.isEnabled())
- {
- TRACER.format(" - move {0} -> {1} ", element.tempIndex, element.destinationIndex); //$NON-NLS-1$
- }
- }
-
- if (element.is(ManipulationConstants.SET_VALUE))
- {
- /*
- * Step 5: SET all elements which have e.type == SET_VALUE by index == e.destinationIdx
- */
- if (setValueStmt == null)
- {
- setValueStmt = accessor.getStatementCache().getPreparedStatement(sqlUpdateValue, ReuseProbability.HIGH);
- idHandler.setCDOID(setValueStmt, 2, id);
- }
-
- setValueStmt.setInt(3, element.destinationIndex);
- getTypeMapping().setValue(setValueStmt, 1, element.value);
- setValueStmt.addBatch();
- setValueCounter++;
-
- if (TRACER.isEnabled())
- {
- TRACER.format(" - set value at {0} to {1} ", element.destinationIndex, element.value); //$NON-NLS-1$
- }
- }
-
- if (element.is(ManipulationConstants.INSERT))
- {
- /*
- * Step 6: INSERT all elements which have e.type == INSERT.
- */
- if (insertStmt == null)
- {
- insertStmt = accessor.getStatementCache().getPreparedStatement(sqlInsertValue, ReuseProbability.HIGH);
- idHandler.setCDOID(insertStmt, 1, id);
- }
-
- insertStmt.setInt(2, element.destinationIndex);
- getTypeMapping().setValue(insertStmt, 3, element.value);
- insertStmt.addBatch();
- insertCounter++;
-
- if (TRACER.isEnabled())
- {
- TRACER.format(" - insert value at {0} : value {1} ", element.destinationIndex, element.value); //$NON-NLS-1$
- }
- }
- }
-
- // finally perform all operations
- if (deleteCounter > 0)
- {
- if (TRACER.isEnabled())
- {
- TRACER.format("Performing {0} delete operations", deleteCounter); //$NON-NLS-1$
- }
-
- DBUtil.executeBatch(deleteStmt, deleteCounter);
- }
-
- if (moveCounter > 0)
- {
- if (TRACER.isEnabled())
- {
- TRACER.format("Performing {0} move operations", moveCounter); //$NON-NLS-1$
- }
-
- DBUtil.executeBatch(moveStmt, moveCounter);
- }
-
- if (insertCounter > 0)
- {
- if (TRACER.isEnabled())
- {
- TRACER.format("Performing {0} insert operations", insertCounter); //$NON-NLS-1$
- }
-
- DBUtil.executeBatch(insertStmt, insertCounter);
- }
-
- if (setValueCounter > 0)
- {
- if (TRACER.isEnabled())
- {
- TRACER.format("Performing {0} set operations", setValueCounter); //$NON-NLS-1$
- }
-
- DBUtil.executeBatch(setValueStmt, setValueCounter);
- }
- }
- catch (SQLException e)
- {
- throw new DBException(e);
- }
- finally
- {
- releaseStatement(accessor, deleteStmt, moveStmt, insertStmt, setValueStmt);
- }
+ visitor.writeResultToDatabase(accessor, id);
}
private void releaseStatement(IDBStoreAccessor accessor, PreparedStatement... stmts)
@@ -558,80 +358,33 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
}
/**
- * Helper method: shift all (destination) indexes in the interval [from,to] (inclusive at both ends) by offset
- * (positive or negative).
+ * @author Eike Stepper
*/
- private void shiftIndexes(int from, int to, int offset)
+ private final class ListDeltaVisitor implements CDOFeatureDeltaVisitor
{
- for (ManipulationElement e : manipulations)
- {
- if (e.destinationIndex >= from && (to == UNBOUNDED_SHIFT || e.destinationIndex <= to))
- {
- e.destinationIndex += offset;
- }
- }
- }
+ private boolean clearFirst;
- /**
- * Find a manipulation item by destination index).
- */
- private ManipulationElement findElement(int index)
- {
- for (ManipulationElement e : manipulations)
- {
- if (e.destinationIndex == index)
- {
- return e;
- }
- }
+ private ArrayList<ManipulationElement> manipulations;
- // never reached
- Assert.isTrue(false);
- return null;
- }
+ /**
+ * Start of a range [tempIndex, tempIndex-1, ...] which lies outside of the normal list indexes and which serve as
+ * temporary space to move items temporarily to get them out of the way of other operations.
+ */
+ private int tempIndex = -1;
- /**
- * Delete an element (used in remove and clear)
- */
- private void deleteItem(ManipulationElement e)
- {
- if (e.is(ManipulationConstants.INSERT))
- {
- // newly inserted items are simply removed, as
- // removing inserted items is equal to no change at all.
- manipulations.remove(e);
- }
- else
+ public ListDeltaVisitor(int oldListSize)
{
- // mark the existing item as to be deleted.
- // (previous MOVE and SET conditions are overridden by setting
- // the exclusive DELETE type).
- e.type = ManipulationConstants.DELETE;
- e.destinationIndex = ManipulationConstants.NO_INDEX;
- }
- }
-
- /**
- * Create a ManipulationElement which represents an element which already is in the list.
- */
- private ManipulationElement createOriginalElement(int index)
- {
- return new ManipulationElement(index, index, ManipulationConstants.NIL, ManipulationConstants.NONE);
- }
+ // reset the clear-flag
+ clearFirst = false;
+ manipulations = new ArrayList<ManipulationElement>(oldListSize);
- /**
- * Create a ManipulationElement which represents an element which is inserted in the list.
- */
- private ManipulationElement createInsertedElement(int index, Object value)
- {
- return new ManipulationElement(ManipulationConstants.NONE, index, value, ManipulationConstants.INSERT);
- }
+ // create list and initialize with original indexes
+ for (int i = 0; i < oldListSize; i++)
+ {
+ manipulations.add(ManipulationElement.createOriginalElement(i));
+ }
+ }
- /**
- * @author Eike Stepper
- */
- private final class ListDeltaVisitor implements CDOFeatureDeltaVisitor
- {
public void visit(CDOAddFeatureDelta delta)
{
if (TRACER.isEnabled())
@@ -643,7 +396,7 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
shiftIndexes(delta.getIndex(), UNBOUNDED_SHIFT, +1);
// create the item
- manipulations.add(createInsertedElement(delta.getIndex(), delta.getValue()));
+ manipulations.add(ManipulationElement.createInsertedElement(delta.getIndex(), delta.getValue()));
}
public void visit(CDORemoveFeatureDelta delta)
@@ -765,12 +518,571 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
// never called
Assert.isTrue(false);
}
+
+ /**
+ * Helper method: shift all (destination) indexes in the interval [from,to] (inclusive at both ends) by offset
+ * (positive or negative).
+ */
+ private void shiftIndexes(int from, int to, int offset)
+ {
+ for (ManipulationElement e : manipulations)
+ {
+ if (e.destinationIndex >= from && (to == UNBOUNDED_SHIFT || e.destinationIndex <= to))
+ {
+ e.destinationIndex += offset;
+ }
+ }
+ }
+
+ /**
+ * Find a manipulation item by destination index).
+ */
+ private ManipulationElement findElement(int index)
+ {
+ for (ManipulationElement e : manipulations)
+ {
+ if (e.destinationIndex == index)
+ {
+ return e;
+ }
+ }
+
+ // never reached
+ Assert.isTrue(false);
+ return null;
+ }
+
+ /**
+ * Delete an element (used in remove and clear)
+ */
+ private void deleteItem(ManipulationElement e)
+ {
+ if (e.is(ManipulationConstants.INSERT))
+ {
+ // newly inserted items are simply removed, as
+ // removing inserted items is equal to no change at all.
+ manipulations.remove(e);
+ }
+ else
+ {
+ // mark the existing item as to be deleted.
+ // (previous MOVE and SET conditions are overridden by setting
+ // the exclusive DELETE type).
+ e.type = ManipulationConstants.DELETE;
+ e.destinationIndex = ManipulationConstants.NO_INDEX;
+ }
+ }
+
+ /**
+ * Called after all deltas are applied an before the results are written to the database. This method post-processes
+ * the manipulation elements in order to minimize database access.
+ */
+ public void postProcess(IDBStoreAccessor accessor, CDOID id)
+ {
+ if (!((HorizontalNonAuditMappingStrategy)getMappingStrategy()).shallForceZeroBasedIndex())
+ {
+ /*
+ * this is an optimization which reduces the amount of modifications on the database to maintain list indexes.
+ * For the optimization, we let go of the assumption that indexes are zero-based. Instead, we work with an
+ * offset at the database level which can change with every change to the list (e.g. if the second element is
+ * removed from a list with 1000 elements, instead of shifting down indexes 2 to 1000 by 1, we shift up index 0
+ * by 1 and have now a list with indexes starting at 1 instead of 0. This optimization is applied by modifying
+ * the list of ManipulationElements, which can be seen as the database modification plan.
+ */
+
+ // first, get the current offset
+ int offsetBefore = getCurrentIndexOffset(accessor, id);
+ if (TRACER.isEnabled())
+ {
+ TRACER.trace("Offset optimization."); //$NON-NLS-1$
+ TRACER.trace("Current offset = " + offsetBefore); //$NON-NLS-1$
+ }
+
+ applyOffsetToSourceIndexes(offsetBefore);
+
+ int offsetAfter;
+
+ if ((long)Math.abs(offsetBefore) + (long)manipulations.size() > Integer.MAX_VALUE)
+ {
+ // security belt for really huge collections or for collections that have been manipulated lots of times
+ // -> do not optimize after this border is crossed. Instead, reset offset for the whole list to a zero-based
+ // index.
+ offsetAfter = 0;
+ }
+ else
+ {
+ offsetAfter = calculateOptimalOffset();
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.trace("New offset = " + -offsetAfter); //$NON-NLS-1$
+ }
+
+ applyOffsetToDestinationIndexes(offsetAfter);
+
+ // make sure temporary indexes do not get in the way of the other operations
+ tempIndex = Math.min(offsetBefore, offsetAfter) - 1;
+ }
+ }
+
+ /**
+ * Calculate the optimal offset wrt the manipulations planned. The optimal offset is the offset which occurs the
+ * most in the manipulations (because letting this offset be neutral leads to the least manipulations. Note: the
+ * zero offset is also regarded as an offset as any other, because selecting an offset != 0 would also lead to
+ * elements with original offset 0 to be moved.
+ */
+ private int calculateOptimalOffset()
+ {
+ HashMap<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
+ int bestOffset = 0;
+ int bestOffsetOccurrence = 0;
+
+ for (ManipulationElement element : manipulations)
+ {
+ int srcIdx = element.sourceIndex;
+ int destIdx = element.destinationIndex;
+ if (srcIdx != ManipulationConstants.NO_INDEX && destIdx != ManipulationConstants.NO_INDEX)
+ {
+ int offset = destIdx - srcIdx;
+ Integer oldOccurrence = occurrences.get(offset);
+ int newOccurrence;
+ if (oldOccurrence == null)
+ {
+ newOccurrence = 1;
+ }
+ else
+ {
+ newOccurrence = oldOccurrence + 1;
+ }
+ occurrences.put(offset, newOccurrence);
+
+ // remember maximum along the way
+ if (newOccurrence > bestOffsetOccurrence)
+ {
+ bestOffsetOccurrence = newOccurrence;
+ bestOffset = offset;
+ }
+ }
+ }
+
+ return bestOffset;
+ }
+
+ private void applyOffsetToSourceIndexes(int offsetBefore)
+ {
+ for (ManipulationElement element : manipulations)
+ {
+ if (element.sourceIndex != ManipulationConstants.NO_INDEX)
+ {
+ element.sourceIndex += offsetBefore;
+ }
+ }
+ }
+
+ private void applyOffsetToDestinationIndexes(int offsetAfter)
+ {
+ for (ManipulationElement element : manipulations)
+ {
+ if (element.destinationIndex != ManipulationConstants.NO_INDEX)
+ {
+ // substract the offset from all indices to make them relative to the new offset
+ element.destinationIndex -= offsetAfter;
+ }
+ }
+ }
+
+ /**
+ * Write calculated changes to the database
+ *
+ * @param accessor
+ */
+ private void writeResultToDatabase(IDBStoreAccessor accessor, CDOID id)
+ {
+ IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement deleteStmt = null;
+ PreparedStatement moveStmt = null;
+ PreparedStatement setValueStmt = null;
+ PreparedStatement insertStmt = null;
+
+ int deleteCounter = 0;
+ int moveCounter = 0;
+ int setValueCounter = 0;
+ int insertCounter = 0;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.trace("Writing to database:"); //$NON-NLS-1$
+ }
+
+ if (clearFirst)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.trace(" - clear list"); //$NON-NLS-1$
+ }
+
+ clearList(accessor, id);
+ }
+
+ try
+ {
+ for (ManipulationElement element : manipulations)
+ {
+ if (element.is(ManipulationConstants.DELETE))
+ {
+ /*
+ * Step 1: DELETE all elements e which have e.is(REMOVE) by e.sourceIdx
+ */
+
+ if (deleteStmt == null)
+ {
+ deleteStmt = statementCache.getPreparedStatement(sqlDeleteItem, ReuseProbability.HIGH);
+ idHandler.setCDOID(deleteStmt, 1, id);
+ }
+
+ deleteStmt.setInt(2, element.sourceIndex);
+ deleteStmt.addBatch();
+ deleteCounter++;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" - delete at {0} ", element.sourceIndex); //$NON-NLS-1$
+ }
+ }
+
+ if (element.is(ManipulationConstants.MOVE))
+ {
+ /*
+ * Step 2: MOVE all elements e (by e.sourceIdx) which have e.is(MOVE) to temporary idx (-1, -2, -3, -4, ...)
+ * and store temporary idx in e.tempIndex
+ */
+ if (moveStmt == null)
+ {
+ moveStmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH);
+ idHandler.setCDOID(moveStmt, 2, id);
+ }
+
+ moveStmt.setInt(3, element.sourceIndex); // from index
+ moveStmt.setInt(1, --tempIndex); // to index
+ element.tempIndex = tempIndex;
+ moveStmt.addBatch();
+ moveCounter++;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" - move {0} -> {1} ", element.sourceIndex, element.tempIndex); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /* now perform deletes and moves ... */
+ if (deleteCounter > 0)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Performing {0} delete operations", deleteCounter); //$NON-NLS-1$
+ }
+
+ DBUtil.executeBatch(deleteStmt, deleteCounter);
+ }
+
+ if (moveCounter > 0)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Performing {0} move operations", moveCounter); //$NON-NLS-1$
+ }
+
+ DBUtil.executeBatch(moveStmt, moveCounter);
+ moveStmt.clearBatch();
+ moveCounter = 0;
+ }
+
+ writeShiftOperations(accessor, id);
+
+ for (ManipulationElement element : manipulations)
+ {
+ if (element.is(ManipulationConstants.MOVE))
+ {
+ /*
+ * Step 4: MOVE all elements e have e.is(MOVE) from e.tempIdx to e.destinationIdx (because we have moved
+ * them before, moveStmt is always initialized
+ */
+ moveStmt.setInt(3, element.tempIndex); // from index
+ moveStmt.setInt(1, element.destinationIndex); // to index
+ moveStmt.addBatch();
+ moveCounter++;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" - move {0} -> {1} ", element.tempIndex, element.destinationIndex); //$NON-NLS-1$
+ }
+ }
+
+ if (element.is(ManipulationConstants.SET_VALUE))
+ {
+ /*
+ * Step 5: SET all elements which have e.type == SET_VALUE by index == e.destinationIdx
+ */
+ if (setValueStmt == null)
+ {
+ setValueStmt = statementCache.getPreparedStatement(sqlUpdateValue, ReuseProbability.HIGH);
+ idHandler.setCDOID(setValueStmt, 2, id);
+ }
+
+ setValueStmt.setInt(3, element.destinationIndex);
+ getTypeMapping().setValue(setValueStmt, 1, element.value);
+ setValueStmt.addBatch();
+ setValueCounter++;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" - set value at {0} to {1} ", element.destinationIndex, element.value); //$NON-NLS-1$
+ }
+ }
+
+ if (element.is(ManipulationConstants.INSERT))
+ {
+ /*
+ * Step 6: INSERT all elements which have e.type == INSERT.
+ */
+ if (insertStmt == null)
+ {
+ insertStmt = statementCache.getPreparedStatement(sqlInsertValue, ReuseProbability.HIGH);
+ idHandler.setCDOID(insertStmt, 1, id);
+ }
+
+ insertStmt.setInt(2, element.destinationIndex);
+ getTypeMapping().setValue(insertStmt, 3, element.value);
+ insertStmt.addBatch();
+ insertCounter++;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" - insert value at {0} : value {1} ", element.destinationIndex, element.value); //$NON-NLS-1$
+ }
+ }
+ }
+
+ if (moveCounter > 0)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Performing {0} move operations", moveCounter); //$NON-NLS-1$
+ }
+
+ DBUtil.executeBatch(moveStmt, moveCounter);
+ }
+
+ if (insertCounter > 0)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Performing {0} insert operations", insertCounter); //$NON-NLS-1$
+ }
+
+ DBUtil.executeBatch(insertStmt, insertCounter);
+ }
+
+ if (setValueCounter > 0)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Performing {0} set operations", setValueCounter); //$NON-NLS-1$
+ }
+
+ DBUtil.executeBatch(setValueStmt, setValueCounter);
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ releaseStatement(accessor, deleteStmt, moveStmt, insertStmt, setValueStmt);
+ }
+ }
+
+ /**
+ * Perform the shift operations to adjust indexes resulting from remove, insert, and move operations.
+ *
+ * @see #writeResultToDatabase(IDBStoreAccessor, CDOID)
+ * @throws SQLException
+ */
+ private void writeShiftOperations(IDBStoreAccessor accessor, CDOID id) throws SQLException
+ {
+ /*
+ * Step 3: shift all elements which have to be shifted up or down because of add, remove or move of other elements
+ * to their proper position. This has to be done in two phases to avoid collisions, as the index has to be unique
+ * and shift up operations have to be executed in top to bottom order.
+ */
+
+ IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
+ int size = manipulations.size();
+
+ LinkedList<ShiftOperation> shiftOperations = new LinkedList<ShiftOperation>();
+
+ /*
+ * If a necessary shift is detected (source and destination indices differ), firstIndex is set to the current
+ * index and currentOffset is set to the offset of the shift operation. When a new offset is detected or the range
+ * is interrupted, we record the range and start a new one if needed.
+ */
+ int rangeStartIndex = ManipulationConstants.NO_INDEX;
+ int rangeOffset = 0;
+ int lastElementIndex = ManipulationConstants.NO_INDEX;
+
+ // iterate through the manipulationElements and collect the necessary operations
+ for (int i = 0; i < size; i++)
+ {
+ ManipulationElement element = manipulations.get(i);
+
+ /*
+ * shift applies only to elements which are not moved, inserted or deleted (i.e. only plain SET_VALUE and NONE
+ * are affected)
+ */
+ if (element.type == ManipulationConstants.NONE || element.type == ManipulationConstants.SET_VALUE)
+ {
+ int elementOffset = element.destinationIndex - element.sourceIndex;
+
+ /*
+ * first make sure if we have to close a previous range. This is the case, if the current element's offset
+ * differs from the rangeOffset and a range is open.
+ */
+ if (elementOffset != rangeOffset && rangeStartIndex != ManipulationConstants.NO_INDEX)
+ {
+ // there is an open range but the rangeOffset differs. We have to close the open range
+ shiftOperations.add(new ShiftOperation(rangeStartIndex, lastElementIndex, rangeOffset));
+ // and reset the state
+ rangeStartIndex = ManipulationConstants.NO_INDEX;
+ rangeOffset = 0;
+ }
+
+ /*
+ * at this point, either a range is open, which means that the current element also fits in the range (i.e.
+ * the offsets match) or no range is open. In the latter case, we have to open one if the current element's
+ * offset is not 0.
+ */
+ if (elementOffset != 0 && rangeStartIndex == ManipulationConstants.NO_INDEX)
+ {
+ rangeStartIndex = element.sourceIndex;
+ rangeOffset = elementOffset;
+ }
+ }
+ else
+ { // shift does not apply to this element because of its type
+ if (rangeStartIndex != ManipulationConstants.NO_INDEX)
+ {
+ // if there is an open range, we have to close and remember it
+ shiftOperations.add(new ShiftOperation(rangeStartIndex, lastElementIndex, rangeOffset));
+ // and reset the state
+ rangeStartIndex = ManipulationConstants.NO_INDEX;
+ rangeOffset = 0;
+ }
+ }
+ lastElementIndex = element.sourceIndex;
+ }
+
+ // after the iteration, we have to make sure that we remember the last open range, if it is there
+ if (rangeStartIndex != ManipulationConstants.NO_INDEX)
+ {
+ shiftOperations.add(new ShiftOperation(rangeStartIndex, lastElementIndex, rangeOffset));
+ }
+
+ /*
+ * now process the operations. Move down operations can be performed directly, move up operations need to be
+ * performed later in the reverse direction
+ */
+ ListIterator<ShiftOperation> operationIt = shiftOperations.listIterator();
+
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement shiftDownStmt = null;
+ int operationCounter = 0;
+
+ try
+ {
+
+ while (operationIt.hasNext())
+ {
+ ShiftOperation operation = operationIt.next();
+ if (operation.offset < 0)
+ {
+ if (shiftDownStmt == null)
+ {
+ shiftDownStmt = statementCache.getPreparedStatement(sqlShiftDownIndex, ReuseProbability.HIGH);
+ idHandler.setCDOID(shiftDownStmt, 2, id);
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" - shift down {0} ", operation); //$NON-NLS-1$
+ }
+
+ shiftDownStmt.setInt(1, operation.offset);
+ shiftDownStmt.setInt(3, operation.startIndex);
+ shiftDownStmt.setInt(4, operation.endIndex);
+ shiftDownStmt.addBatch();
+ operationCounter++;
+
+ operationIt.remove();
+ }
+ }
+ if (operationCounter > 0)
+ {
+ DBUtil.executeBatch(shiftDownStmt, operationCounter, false);
+ }
+ }
+ finally
+ {
+ releaseStatement(accessor, shiftDownStmt);
+ }
+
+ PreparedStatement shiftUpStmt = null;
+ operationCounter = 0;
+
+ try
+ {
+
+ while (operationIt.hasPrevious())
+ {
+ ShiftOperation operation = operationIt.previous();
+ if (shiftUpStmt == null)
+ {
+ shiftUpStmt = statementCache.getPreparedStatement(sqlShiftUpIndex, ReuseProbability.HIGH);
+ idHandler.setCDOID(shiftUpStmt, 2, id);
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" - shift up {0} ", operation); //$NON-NLS-1$
+ }
+
+ shiftUpStmt.setInt(1, operation.offset);
+ shiftUpStmt.setInt(3, operation.startIndex);
+ shiftUpStmt.setInt(4, operation.endIndex);
+ shiftUpStmt.addBatch();
+ operationCounter++;
+ }
+
+ if (operationCounter > 0)
+ {
+ DBUtil.executeBatch(shiftUpStmt, operationCounter, false);
+ }
+ }
+ finally
+ {
+ releaseStatement(accessor, shiftUpStmt);
+ }
+ }
+
}
/**
* @author Eike Stepper
*/
- private static final class ManipulationConstants
+ private static interface ManipulationConstants
{
public static final int NO_INDEX = Integer.MIN_VALUE;
@@ -790,7 +1102,7 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
/**
* @author Eike Stepper
*/
- private static final class ManipulationElement
+ private static final class ManipulationElement implements ManipulationConstants
{
public int type;
@@ -805,12 +1117,28 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
public ManipulationElement(int srcIdx, int dstIdx, Object val, int t)
{
sourceIndex = srcIdx;
- tempIndex = ManipulationConstants.NONE;
+ tempIndex = NO_INDEX;
destinationIndex = dstIdx;
value = val;
type = t;
}
+ /**
+ * Create a ManipulationElement which represents an element which already is in the list.
+ */
+ public static ManipulationElement createOriginalElement(int index)
+ {
+ return new ManipulationElement(index, index, NIL, NONE);
+ }
+
+ /**
+ * Create a ManipulationElement which represents an element which is inserted in the list.
+ */
+ public static ManipulationElement createInsertedElement(int index, Object value)
+ {
+ return new ManipulationElement(NO_INDEX, index, value, ManipulationConstants.INSERT);
+ }
+
public boolean is(int t)
{
return (type & t) > 0;
@@ -821,4 +1149,29 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
type |= t;
}
}
+
+ /**
+ * @author Eike Stepper
+ */
+ private static class ShiftOperation
+ {
+ final int startIndex;
+
+ final int endIndex;
+
+ final int offset;
+
+ ShiftOperation(int startIndex, int endIndex, int offset)
+ {
+ this.startIndex = startIndex;
+ this.endIndex = endIndex;
+ this.offset = offset;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "range [" + startIndex + ".." + endIndex + "] offset " + offset;
+ }
+ }
}
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/RevisionDeltaTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/RevisionDeltaTest.java
index 3659cee704..f48e803fb0 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/RevisionDeltaTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/RevisionDeltaTest.java
@@ -531,6 +531,138 @@ public class RevisionDeltaTest extends AbstractCDOTest
});
}
+ public void testMultipleInserts()
+ {
+ testStoreDelta(new ListManipulator()
+ {
+ @SuppressWarnings("unchecked")
+ public void doManipulations(EList<?> list)
+ {
+ EList<Company> l = (EList<Company>)list;
+ Company company = getModel1Factory().createCompany();
+ company.setName("NewEntry 1");
+ l.add(7, company);
+ company = getModel1Factory().createCompany();
+ company.setName("NewEntry 2");
+ l.add(12, company);
+ }
+ });
+ }
+
+ public void testInsertAndRemove()
+ {
+ testStoreDelta(new ListManipulator()
+ {
+ @SuppressWarnings("unchecked")
+ public void doManipulations(EList<?> list)
+ {
+ EList<Company> l = (EList<Company>)list;
+ Company company = getModel1Factory().createCompany();
+ company.setName("NewEntry 1");
+ l.add(7, company);
+ l.remove(12);
+ }
+ });
+ }
+
+ public void testInsertAndMove()
+ {
+ testStoreDelta(new ListManipulator()
+ {
+ @SuppressWarnings("unchecked")
+ public void doManipulations(EList<?> list)
+ {
+ EList<Company> l = (EList<Company>)list;
+ Company company = getModel1Factory().createCompany();
+ company.setName("NewEntry 1");
+ l.add(7, company);
+ l.move(12, 7);
+ }
+ });
+ }
+
+ public void testMoveAndDelete()
+ {
+ testStoreDelta(new ListManipulator()
+ {
+ @SuppressWarnings("unchecked")
+ public void doManipulations(EList<?> list)
+ {
+ EList<Company> l = (EList<Company>)list;
+ l.move(12, 7);
+ l.remove(12);
+ }
+ });
+ }
+
+ public void testInsertAndMoveAndRemove()
+ {
+ testStoreDelta(new ListManipulator()
+ {
+ @SuppressWarnings("unchecked")
+ public void doManipulations(EList<?> list)
+ {
+ EList<Company> l = (EList<Company>)list;
+ Company company = getModel1Factory().createCompany();
+ company.setName("NewEntry 1");
+ l.add(7, company);
+ l.move(12, 7);
+ l.remove(12);
+ }
+ });
+ }
+
+ public void testInsertAndSet()
+ {
+ testStoreDelta(new ListManipulator()
+ {
+ @SuppressWarnings("unchecked")
+ public void doManipulations(EList<?> list)
+ {
+ EList<Company> l = (EList<Company>)list;
+ Company company = getModel1Factory().createCompany();
+ company.setName("NewEntry 1");
+ l.add(7, company);
+ Company company2 = getModel1Factory().createCompany();
+ company2.setName("NewEntry 2");
+ l.set(7, company2);
+ l.add(19, company); // <- needed because the set operation makes the company a dangling reference
+ }
+ });
+ }
+
+ public void testSetAndRemove()
+ {
+ testStoreDelta(new ListManipulator()
+ {
+ @SuppressWarnings("unchecked")
+ public void doManipulations(EList<?> list)
+ {
+ EList<Company> l = (EList<Company>)list;
+ Company company = getModel1Factory().createCompany();
+ company.setName("NewEntry 1");
+ Company oldCompany = l.get(7);
+ l.set(7, company);
+ l.add(19, oldCompany);
+ l.remove(7); // <- needed because the set operation makes the company a dangling reference
+ }
+ });
+ }
+
+ public void testMultipleRemoves()
+ {
+ testStoreDelta(new ListManipulator()
+ {
+ @SuppressWarnings("unchecked")
+ public void doManipulations(EList<?> list)
+ {
+ EList<Company> l = (EList<Company>)list;
+ l.remove(7);
+ l.remove(12);
+ }
+ });
+ }
+
private InternalCDORevision getCopyCDORevision(Object object)
{
return (InternalCDORevision)CDOUtil.getCDOObject((EObject)object).cdoRevision().copy();
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/performance/DeletePerformanceTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/performance/DeletePerformanceTest.java
index 4e053e412a..c1ae50d895 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/performance/DeletePerformanceTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/performance/DeletePerformanceTest.java
@@ -55,17 +55,17 @@ public class DeletePerformanceTest extends PerformanceTest
}
@CleanRepositoriesBefore
- public void testDeleteRandom() throws Exception
+ public void _testDeleteRandom() throws Exception
{
Company company = initModel();
CDOTransaction transaction = (CDOTransaction)CDOUtil.getCDOObject(company).cdoView();
msg("Starting to remove elements ...");
- for (int i = 0; i < AMOUNT_ELEMENTS / 2; i++)
+ for (int i = 0; i < AMOUNT_ELEMENTS / 4; i++)
{
int currentSize = AMOUNT_ELEMENTS - i;
- int indexToRemove = random.nextInt(currentSize);
+ int indexToRemove = random.nextInt(currentSize - 1);
company.getCategories().remove(indexToRemove);
@@ -76,14 +76,14 @@ public class DeletePerformanceTest extends PerformanceTest
}
@CleanRepositoriesBefore
- public void testDeleteDeterministic() throws Exception
+ public void testDeleteEveryOther() throws Exception
{
Company company = initModel();
CDOTransaction transaction = (CDOTransaction)CDOUtil.getCDOObject(company).cdoView();
msg("Starting to remove elements ...");
- for (int i = 0; i < AMOUNT_ELEMENTS / 2; i++)
+ for (int i = 0; i < AMOUNT_ELEMENTS / 4; i++)
{
int indexToRemove = AMOUNT_ELEMENTS / 2 - i;
@@ -95,4 +95,23 @@ public class DeletePerformanceTest extends PerformanceTest
}
}
+ @CleanRepositoriesBefore
+ public void _testDeleteAtBeginning() throws Exception
+ {
+ Company company = initModel();
+ CDOTransaction transaction = (CDOTransaction)CDOUtil.getCDOObject(company).cdoView();
+
+ msg("Starting to remove elements ...");
+
+ for (int i = 0; i < AMOUNT_ELEMENTS / 4; i++)
+ {
+ int indexToRemove = 0;
+
+ company.getCategories().remove(indexToRemove);
+
+ startProbing();
+ transaction.commit();
+ stopProbing();
+ }
+ }
}
diff --git a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/DBUtil.java b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/DBUtil.java
index 0aa500536f..ac34132b4b 100644
--- a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/DBUtil.java
+++ b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/DBUtil.java
@@ -523,6 +523,14 @@ public final class DBUtil
*/
public static void executeBatch(PreparedStatement stmt, int counter)
{
+ executeBatch(stmt, counter, true);
+ }
+
+ /**
+ * @since 4.1
+ */
+ public static void executeBatch(PreparedStatement stmt, int counter, boolean checkExactlyOne)
+ {
try
{
int[] results = stmt.executeBatch();
@@ -534,10 +542,14 @@ public final class DBUtil
for (int i = 0; i < results.length; i++)
{
int result = results[i];
- if (result != 1 && result != Statement.SUCCESS_NO_INFO)
+ if (result < 0 && result != Statement.SUCCESS_NO_INFO)
{
throw new DBException("Result " + i + " is not successful: " + result);
}
+ else if (checkExactlyOne && result != 1)
+ {
+ throw new DBException("Result " + i + " did not affect exactly one row: " + result);
+ }
}
}
catch (SQLException ex)

Back to the top