Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Winkler2011-12-18 02:00:06 -0500
committerStefan Winkler2011-12-21 06:22:40 -0500
commit7d3ea9bf420ff0308b991c6803a586846222aecb (patch)
tree7cb6d170b2df2bfc847b0bc106eab9c3ebaba2c2
parent0df2f722b0f09001607ff529c87dad81167ade0f (diff)
downloadcdo-7d3ea9bf420ff0308b991c6803a586846222aecb.tar.gz
cdo-7d3ea9bf420ff0308b991c6803a586846222aecb.tar.xz
cdo-7d3ea9bf420ff0308b991c6803a586846222aecb.zip
[366686] [DB] Reduce amount of update statements for non-audit mode
https://bugs.eclipse.org/bugs/show_bug.cgi?id=366686 - Added clearBatch() ater execution of first group of move statement. - Refactoring to avoid potential thread-safety-violation with clearFirst and manipulations attributes. - Second optimization: release assumption of zero-based indexes (DeQue-like behaviour) - bugfixes
-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/internal/db/DBStoreChunkReader.java13
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BasicAbstractListTableMapping.java21
-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.java1106
5 files changed, 687 insertions, 475 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/internal/db/DBStoreChunkReader.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreChunkReader.java
index b98c95fc73..13ae632f1a 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
@@ -17,6 +17,7 @@ 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.IMappingStrategy;
+import org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.BasicAbstractListTableMapping;
import org.eclipse.emf.cdo.spi.server.StoreChunkReader;
import org.eclipse.emf.ecore.EStructuralFeature;
@@ -52,9 +53,8 @@ public class DBStoreChunkReader extends StoreChunkReader implements IDBStoreChun
super.addSimpleChunk(index);
prepareAddition();
- builder.append(CDODBSchema.LIST_IDX);
- builder.append('=');
- builder.append(index);
+ ((BasicAbstractListTableMapping)referenceMapping).addSimpleChunkWhere(getAccessor(), getRevision().getID(),
+ builder, index);
}
@Override
@@ -63,11 +63,8 @@ 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);
+ ((BasicAbstractListTableMapping)referenceMapping).addRangedChunkWhere(getAccessor(), getRevision().getID(),
+ builder, fromIndex, toIndex);
}
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..7add335a82 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.common.id.CDOID;
import org.eclipse.emf.cdo.server.db.mapping.IListMapping;
import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
+import org.eclipse.emf.cdo.server.internal.db.CDODBSchema;
+import org.eclipse.emf.cdo.server.internal.db.DBStoreAccessor;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
@@ -49,4 +52,22 @@ public abstract class BasicAbstractListTableMapping implements IListMapping
{
return feature;
}
+
+ public void addSimpleChunkWhere(DBStoreAccessor accessor, CDOID cdoid, StringBuilder builder, int index)
+ {
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append('=');
+ builder.append(index);
+ }
+
+ public void addRangedChunkWhere(DBStoreAccessor 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 6f732cf928..aff10e9fb5 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
@@ -31,6 +31,7 @@ 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.internal.db.CDODBSchema;
+import org.eclipse.emf.cdo.server.internal.db.DBStoreAccessor;
import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
@@ -45,8 +46,10 @@ 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;
@@ -64,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;
@@ -87,6 +79,8 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
private String sqlMassUpdateIndex;
+ private String sqlReadCurrentIndexOffset;
+
public NonAuditListTableMapping(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature)
{
super(mappingStrategy, eClass, feature);
@@ -167,6 +161,32 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
builder.append(CDODBSchema.LIST_IDX);
builder.append(" BETWEEN ? AND ?"); //$NON-NLS-1$
sqlMassUpdateIndex = builder.toString();
+
+ // ----------- 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(DBStoreAccessor accessor, CDOID cdoid, StringBuilder builder, int index)
+ {
+ int offset = getCurrentIndexOffset(accessor, cdoid);
+ super.addSimpleChunkWhere(accessor, cdoid, builder, index + offset);
+ }
+
+ @Override
+ public void addRangedChunkWhere(DBStoreAccessor accessor, CDOID cdoid, StringBuilder builder, int fromIndex,
+ int toIndex)
+ {
+ int offset = getCurrentIndexOffset(accessor, cdoid);
+ super.addRangedChunkWhere(accessor, cdoid, builder, fromIndex + offset, toIndex + offset);
}
@Override
@@ -220,6 +240,35 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
}
}
+ public int getCurrentIndexOffset(IDBStoreAccessor accessor, CDOID id)
+ {
+ PreparedStatement stmt = null;
+ ResultSet rset = null;
+
+ try
+ {
+ stmt = accessor.getStatementCache().getPreparedStatement(sqlReadCurrentIndexOffset, ReuseProbability.HIGH);
+ getMappingStrategy().getStore().getIDHandler().setCDOID(stmt, 1, id);
+ rset = stmt.executeQuery();
+ if (!rset.first())
+ {
+ // 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);
+ }
+ }
+
public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int branchId, int oldVersion,
final int newVersion, long created, CDOListFeatureDelta delta)
{
@@ -229,32 +278,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())
{
@@ -266,377 +297,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$
- }
- }
- }
-
- /* 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);
- 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 = 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$
- }
- }
- }
-
- 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
- {
- PreparedStatement shiftIndicesStmt = null;
- try
- {
- /*
- * 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;
-
- // 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, i - 1, 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 = i;
- 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, i - 1, rangeOffset));
- // and reset the state
- rangeStartIndex = ManipulationConstants.NO_INDEX;
- rangeOffset = 0;
- }
- }
- }
-
- // 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, size - 1, rangeOffset));
- }
-
- /*
- * now process the operations. Move down operations can be performed directly, move up operations need to be
- * performed later in the reverse direction
- */
- int operationCounter = shiftOperations.size();
- ListIterator<ShiftOperation> operationIt = shiftOperations.listIterator();
-
- while (operationIt.hasNext())
- {
- ShiftOperation operation = operationIt.next();
- if (operation.offset < 0)
- {
- if (shiftIndicesStmt == null)
- {
- shiftIndicesStmt = accessor.getStatementCache().getPreparedStatement(sqlMassUpdateIndex,
- ReuseProbability.HIGH);
- idHandler.setCDOID(shiftIndicesStmt, 2, id);
- }
-
- if (TRACER.isEnabled())
- {
- TRACER.format(" - shift {0} ", operation); //$NON-NLS-1$
- }
-
- shiftIndicesStmt.setInt(1, operation.offset);
- shiftIndicesStmt.setInt(3, operation.startIndex);
- shiftIndicesStmt.setInt(4, operation.endIndex);
- shiftIndicesStmt.addBatch();
-
- operationIt.remove();
- }
- }
- while (operationIt.hasPrevious())
- {
- ShiftOperation operation = operationIt.previous();
- if (shiftIndicesStmt == null)
- {
- shiftIndicesStmt = accessor.getStatementCache().getPreparedStatement(sqlMassUpdateIndex,
- ReuseProbability.HIGH);
- idHandler.setCDOID(shiftIndicesStmt, 2, id);
- }
-
- if (TRACER.isEnabled())
- {
- TRACER.format(" - shift {0} ", operation); //$NON-NLS-1$
- }
-
- shiftIndicesStmt.setInt(1, operation.offset);
- shiftIndicesStmt.setInt(3, operation.startIndex);
- shiftIndicesStmt.setInt(4, operation.endIndex);
- shiftIndicesStmt.addBatch();
- }
-
- if (operationCounter > 0)
- {
- DBUtil.executeBatch(shiftIndicesStmt, operationCounter, false);
- }
- }
- finally
- {
- releaseStatement(accessor, shiftIndicesStmt);
- }
+ visitor.writeResultToDatabase(accessor, id);
}
private void releaseStatement(IDBStoreAccessor accessor, PreparedStatement... stmts)
@@ -683,80 +347,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())
@@ -768,7 +385,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)
@@ -890,6 +507,545 @@ 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();
+ 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 = 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$
+ }
+ }
+ }
+
+ /* 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 = 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$
+ }
+ }
+ }
+
+ 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
+ {
+ PreparedStatement shiftIndicesStmt = null;
+ try
+ {
+ /*
+ * 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
+ */
+ int operationCounter = shiftOperations.size();
+ ListIterator<ShiftOperation> operationIt = shiftOperations.listIterator();
+
+ while (operationIt.hasNext())
+ {
+ ShiftOperation operation = operationIt.next();
+ if (operation.offset < 0)
+ {
+ if (shiftIndicesStmt == null)
+ {
+ shiftIndicesStmt = accessor.getStatementCache().getPreparedStatement(sqlMassUpdateIndex,
+ ReuseProbability.HIGH);
+ idHandler.setCDOID(shiftIndicesStmt, 2, id);
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" - shift {0} ", operation); //$NON-NLS-1$
+ }
+
+ shiftIndicesStmt.setInt(1, operation.offset);
+ shiftIndicesStmt.setInt(3, operation.startIndex);
+ shiftIndicesStmt.setInt(4, operation.endIndex);
+ shiftIndicesStmt.addBatch();
+
+ operationIt.remove();
+ }
+ }
+ while (operationIt.hasPrevious())
+ {
+ ShiftOperation operation = operationIt.previous();
+ if (shiftIndicesStmt == null)
+ {
+ shiftIndicesStmt = accessor.getStatementCache().getPreparedStatement(sqlMassUpdateIndex,
+ ReuseProbability.HIGH);
+ idHandler.setCDOID(shiftIndicesStmt, 2, id);
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" - shift {0} ", operation); //$NON-NLS-1$
+ }
+
+ shiftIndicesStmt.setInt(1, operation.offset);
+ shiftIndicesStmt.setInt(3, operation.startIndex);
+ shiftIndicesStmt.setInt(4, operation.endIndex);
+ shiftIndicesStmt.addBatch();
+ }
+
+ if (operationCounter > 0)
+ {
+ DBUtil.executeBatch(shiftIndicesStmt, operationCounter, false);
+ }
+ }
+ finally
+ {
+ releaseStatement(accessor, shiftIndicesStmt);
+ }
+ }
+
}
/**
@@ -930,12 +1086,28 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
public ManipulationElement(int srcIdx, int dstIdx, Object val, int t)
{
sourceIndex = srcIdx;
- tempIndex = ManipulationConstants.NONE;
+ tempIndex = ManipulationConstants.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, ManipulationConstants.NIL, ManipulationConstants.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(ManipulationConstants.NO_INDEX, index, value, ManipulationConstants.INSERT);
+ }
+
public boolean is(int t)
{
return (type & t) > 0;

Back to the top