diff options
Diffstat (limited to 'org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java')
-rw-r--r-- | org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java | 825 |
1 files changed, 825 insertions, 0 deletions
diff --git a/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java b/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java new file mode 100644 index 0000000000..92d96aad20 --- /dev/null +++ b/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java @@ -0,0 +1,825 @@ +/** + * 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 + * Stefan Winkler - 271444: [DB] Multiple refactorings bug 271444 + * Stefan Winkler - Bug 329025: [DB] Support branching for range-based mapping strategy + */ +package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal; + +import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.revision.CDORevision; +import org.eclipse.emf.cdo.common.revision.delta.CDOAddFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOContainerFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDeltaVisitor; +import org.eclipse.emf.cdo.common.revision.delta.CDOListFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOMoveFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta; +import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; +import org.eclipse.emf.cdo.server.db.IIDHandler; +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.bundle.OM; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; + +import org.eclipse.net4j.db.DBException; +import org.eclipse.net4j.db.DBType; +import org.eclipse.net4j.db.DBUtil; +import org.eclipse.net4j.util.om.trace.ContextTracer; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EStructuralFeature; + +import org.eclipse.core.runtime.Assert; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; + +/** + * This is a list-to-table mapping optimized for non-audit-mode. It doesn't care about version and has delta support. + * + * @author Eike Stepper + * @since 2.0 + */ +public class NonAuditListTableMapping extends AbstractListTableMapping implements IListMappingDeltaSupport +{ + private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, NonAuditListTableMapping.class); + + private FieldInfo[] keyFields; + + 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; + + private String sqlUpdateIndex; + + private String sqlInsertValue; + + private String sqlDeleteItem; + + public NonAuditListTableMapping(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature) + { + super(mappingStrategy, eClass, feature); + initSQLStrings(); + } + + private void initSQLStrings() + { + // ----------- clear list ------------------------- + StringBuilder builder = new StringBuilder(); + + builder.append("DELETE FROM "); //$NON-NLS-1$ + builder.append(getTable()); + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append("=? "); //$NON-NLS-1$ + + sqlClear = builder.toString(); + + builder.append(" AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_IDX); + builder.append("=? "); //$NON-NLS-1$ + + sqlDeleteItem = builder.toString(); + + // ----------- update one item -------------------- + builder = new StringBuilder(); + builder.append("UPDATE "); //$NON-NLS-1$ + builder.append(getTable()); + builder.append(" SET "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_VALUE); + builder.append("=? "); //$NON-NLS-1$ + builder.append(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append("=? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_IDX); + builder.append("=? "); //$NON-NLS-1$ + sqlUpdateValue = builder.toString(); + + // ----------- insert one item -------------------- + builder = new StringBuilder(); + builder.append("INSERT INTO "); //$NON-NLS-1$ + builder.append(getTable()); + builder.append(" ("); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append(", "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_IDX); + builder.append(", "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_VALUE); + builder.append(") VALUES(?, ?, ?) "); //$NON-NLS-1$ + sqlInsertValue = builder.toString(); + + // ----------- update one item index -------------- + 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(" WHERE "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_REVISION_ID); + builder.append("=? AND "); //$NON-NLS-1$ + builder.append(CDODBSchema.LIST_IDX); + builder.append("=? "); //$NON-NLS-1$ + sqlUpdateIndex = builder.toString(); + } + + @Override + protected FieldInfo[] getKeyFields() + { + if (keyFields == null) + { + DBType dbType = getMappingStrategy().getStore().getIDHandler().getDBType(); + keyFields = new FieldInfo[] { new FieldInfo(CDODBSchema.LIST_REVISION_ID, dbType) }; + } + + return keyFields; + } + + @Override + protected void setKeyFields(PreparedStatement stmt, CDORevision revision) throws SQLException + { + getMappingStrategy().getStore().getIDHandler().setCDOID(stmt, 1, revision.getID()); + } + + public void objectDetached(IDBStoreAccessor accessor, CDOID id, long revised) + { + clearList(accessor, id); + } + + /** + * Clear a list of a given revision. + * + * @param accessor + * the accessor to use + * @param id + * the id of the revision from which to remove all items + */ + public void clearList(IDBStoreAccessor accessor, CDOID id) + { + PreparedStatement stmt = null; + + try + { + stmt = accessor.getStatementCache().getPreparedStatement(sqlClear, ReuseProbability.HIGH); + getMappingStrategy().getStore().getIDHandler().setCDOID(stmt, 1, id); + DBUtil.update(stmt, false); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().releasePreparedStatement(stmt); + } + } + + public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int branchId, int oldVersion, + final int newVersion, long created, CDOListFeatureDelta delta) + { + CDOBranchPoint main = accessor.getStore().getRepository().getBranchManager().getMainBranch().getHead(); + + InternalCDORevision originalRevision = (InternalCDORevision)accessor.getStore().getRepository() + .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(); + + if (TRACER.isEnabled()) + { + TRACER.format("Procssing deltas..."); //$NON-NLS-1$ + } + + for (CDOFeatureDelta listDelta : delta.getListChanges()) + { + 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. + + // 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.format("Writing to database:"); //$NON-NLS-1$ + } + + if (clearFirst) + { + if (TRACER.isEnabled()) + { + TRACER.format(" - 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); + } + } + + private void releaseStatement(IDBStoreAccessor accessor, PreparedStatement... stmts) + { + Throwable t = null; + + for (PreparedStatement stmt : stmts) + { + try + { + if (stmt != null) + { + try + { + stmt.clearBatch(); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().releasePreparedStatement(stmt); + } + } + } + catch (Throwable th) + { + if (t == null) + { + // remember first exception + t = th; + } + + // more exceptions go to the log + OM.LOG.error(t); + } + } + + if (t != null) + { + throw new DBException(t); + } + } + + /** + * 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; + } + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * @author Eike Stepper + */ + private final class ListDeltaVisitor implements CDOFeatureDeltaVisitor + { + public void visit(CDOAddFeatureDelta delta) + { + if (TRACER.isEnabled()) + { + TRACER.format(" - insert at {0} value {1}", delta.getIndex(), delta.getValue()); //$NON-NLS-1$ + } + + // make room for the new item + shiftIndexes(delta.getIndex(), UNBOUNDED_SHIFT, +1); + + // create the item + manipulations.add(createInsertedElement(delta.getIndex(), delta.getValue())); + } + + public void visit(CDORemoveFeatureDelta delta) + { + if (TRACER.isEnabled()) + { + TRACER.format(" - remove at {0}", delta.getIndex()); //$NON-NLS-1$ + } + + ManipulationElement e = findElement(delta.getIndex()); + deleteItem(e); + + // fill the gap by shifting all subsequent items down + shiftIndexes(delta.getIndex() + 1, UNBOUNDED_SHIFT, -1); + } + + public void visit(CDOSetFeatureDelta delta) + { + if (TRACER.isEnabled()) + { + TRACER.format(" - set at {0} value {1}", delta.getIndex(), delta.getValue()); //$NON-NLS-1$ + } + + ManipulationElement e = findElement(delta.getIndex()); + // set the new value + e.value = delta.getValue(); + + // if the item is freshly inserted we do not set the SET-mark. + // setting the value of a new item results in inserting with the + // new value at once. + if (!e.is(ManipulationConstants.INSERT)) + { + // else mark the existing item to be set to a new value + e.addType(ManipulationConstants.SET_VALUE); + } + } + + public void visit(CDOClearFeatureDelta delta) + { + if (TRACER.isEnabled()) + { + TRACER.format(" - clear list"); //$NON-NLS-1$ + } + + // set the clear-flag + clearFirst = true; + + // and also clear all manipulation items + manipulations.clear(); + } + + public void visit(CDOMoveFeatureDelta delta) + { + int fromIdx = delta.getOldPosition(); + int toIdx = delta.getNewPosition(); + + if (TRACER.isEnabled()) + { + TRACER.format(" - move {0} -> {1}", fromIdx, toIdx); //$NON-NLS-1$ + } + + // ignore the trivial case + if (fromIdx == toIdx) + { + return; + } + + ManipulationElement e = findElement(fromIdx); + + // adjust indexes and shift either up or down + if (fromIdx < toIdx) + { + shiftIndexes(fromIdx + 1, toIdx, -1); + } + else + { // fromIdx > toIdx here + shiftIndexes(toIdx, fromIdx - 1, +1); + } + + // set the new index + e.destinationIndex = toIdx; + + // if it is a new element, no MOVE mark needed, because we insert it + // at the new position + if (!e.is(ManipulationConstants.INSERT)) + { + // else we need to handle the move of an existing item + e.addType(ManipulationConstants.MOVE); + } + } + + public void visit(CDOUnsetFeatureDelta delta) + { + if (delta.getFeature().isUnsettable()) + { + Assert.isTrue(false); + } + + if (TRACER.isEnabled()) + { + TRACER.format(" - unset list"); //$NON-NLS-1$ + } + + // set the clear-flag + clearFirst = true; + + // and also clear all manipulation items + manipulations.clear(); + } + + public void visit(CDOListFeatureDelta delta) + { + // never called + Assert.isTrue(false); + } + + public void visit(CDOContainerFeatureDelta delta) + { + // never called + Assert.isTrue(false); + } + } + + /** + * @author Eike Stepper + */ + private static final class ManipulationConstants + { + public static final int NO_INDEX = Integer.MIN_VALUE; + + public static final int DELETE = 1 << 4; + + public static final int INSERT = 1 << 3; + + public static final int MOVE = 1 << 2; + + public static final int SET_VALUE = 1 << 1; + + public static final Object NIL = new Object(); + + public static final int NONE = 0; + } + + /** + * @author Eike Stepper + */ + private static final class ManipulationElement + { + public int type; + + public int sourceIndex; + + public int tempIndex; + + public int destinationIndex; + + public Object value; + + public ManipulationElement(int srcIdx, int dstIdx, Object val, int t) + { + sourceIndex = srcIdx; + tempIndex = ManipulationConstants.NONE; + destinationIndex = dstIdx; + value = val; + type = t; + } + + public boolean is(int t) + { + return (type & t) > 0; + } + + public void addType(int t) + { + type |= t; + } + } +} |