blob: 0f8c57ea8095279675da67e757b826d10e30554a [file] [log] [blame]
/**
* 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
* Christopher Albert - 254455: [DB] Support FeatureMaps bug 254455
* 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.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;
import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability;
import org.eclipse.emf.cdo.server.db.mapping.IListMappingDeltaSupport;
import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
import org.eclipse.emf.cdo.server.db.mapping.ITypeMapping;
import org.eclipse.emf.cdo.server.internal.db.CDODBSchema;
import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBType;
import org.eclipse.net4j.db.DBUtil;
import org.eclipse.net4j.util.ImplementationError;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.FeatureMap;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Iterator;
/**
* This is a featuremap-to-table mapping optimized for non-audit-mode. It doesn't care about version and has delta
* support.
*
* @author Eike Stepper
* @since 3.0
*/
public class NonAuditFeatureMapTableMapping extends AbstractFeatureMapTableMapping implements IListMappingDeltaSupport
{
private FieldInfo[] keyFields;
private static final int TEMP_INDEX = -1;
private static final int UNBOUNDED_MOVE = -1;
private String sqlClear;
private String sqlUpdateIndex;
private String sqlUpdateValue;
private String sqlDeleteItem;
private String sqlMoveDownWithLimit;
private String sqlMoveDown;
private String sqlMoveUpWithLimit;
private String sqlMoveUp;
public NonAuditFeatureMapTableMapping(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature)
{
super(mappingStrategy, eClass, feature);
initSQLStrings();
}
private void initSQLStrings()
{
// TODO: add key fields length support
StringBuilder builder = new StringBuilder();
// ----------- clear list -------------------------
builder.append("DELETE FROM "); //$NON-NLS-1$
builder.append(getTable());
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
builder.append("=? "); //$NON-NLS-1$
sqlClear = builder.toString();
builder.append(" AND "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append("=? "); //$NON-NLS-1$
sqlDeleteItem = 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.FEATUREMAP_IDX);
builder.append("=? "); //$NON-NLS-1$
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
builder.append("=? AND "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append("=? "); //$NON-NLS-1$
sqlUpdateIndex = builder.toString();
// ----------- update one item value --------------
builder = new StringBuilder();
builder.append("UPDATE "); //$NON-NLS-1$
builder.append(getTable());
builder.append(" SET "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_TAG);
builder.append("=?,"); //$NON-NLS-1$
Iterator<String> iter = getColumnNames().iterator();
while (iter.hasNext())
{
String column = iter.next();
builder.append(column);
builder.append("=?"); //$NON-NLS-1$
if (iter.hasNext())
{
builder.append(", "); //$NON-NLS-1$
}
}
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
builder.append("=? AND "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append("=? "); //$NON-NLS-1$
sqlUpdateValue = builder.toString();
// ----------- move down --------------
builder = new StringBuilder();
builder.append("UPDATE "); //$NON-NLS-1$
builder.append(getTable());
builder.append(" SET "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append("="); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append("-1 WHERE "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
builder.append("=? AND "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append(">? "); //$NON-NLS-1$
sqlMoveDown = builder.toString();
builder.append(" AND "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append("<=?"); //$NON-NLS-1$
sqlMoveDownWithLimit = builder.toString();
// ----------- move up --------------
builder = new StringBuilder();
builder.append("UPDATE "); //$NON-NLS-1$
builder.append(getTable());
builder.append(" SET "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append("="); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append("+1 WHERE "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
builder.append("=? AND "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append(">=? "); //$NON-NLS-1$
sqlMoveUp = builder.toString();
builder.append(" AND "); //$NON-NLS-1$
builder.append(CDODBSchema.FEATUREMAP_IDX);
builder.append("<?"); //$NON-NLS-1$
sqlMoveUpWithLimit = builder.toString();
}
@Override
protected FieldInfo[] getKeyFields()
{
if (keyFields == null)
{
DBType dbType = getMappingStrategy().getStore().getIDHandler().getDBType();
keyFields = new FieldInfo[] { new FieldInfo(CDODBSchema.FEATUREMAP_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)
{
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(sqlClear, ReuseProbability.HIGH);
getMappingStrategy().getStore().getIDHandler().setCDOID(stmt, 1, id);
DBUtil.update(stmt, false);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
/**
* Insert a list item at a specified position.
*
* @param accessor
* the accessor to use
* @param id
* the id of the revision to insert the value
* @param index
* the index where to insert the element
* @param value
* the value to insert.
*/
public void insertListItem(IDBStoreAccessor accessor, CDOID id, int index, Object value, long timestamp)
{
move1up(accessor, id, index, UNBOUNDED_MOVE);
insertValue(accessor, id, index, value, timestamp);
}
private void insertValue(IDBStoreAccessor accessor, CDOID id, int index, Object value, long timestamp)
{
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
FeatureMap.Entry entry = (FeatureMap.Entry)value;
EStructuralFeature entryFeature = entry.getEStructuralFeature();
CDOID tag = getTagByFeature(entryFeature, timestamp);
String columnName = getColumnName(tag);
String sql = sqlInsert;
stmt = statementCache.getPreparedStatement(sql, ReuseProbability.HIGH);
idHandler.setCDOID(stmt, 1, id);
int column = getKeyFields().length + 1;
for (int i = 0; i < getColumnNames().size(); i++)
{
if (getColumnNames().get(i).equals(columnName))
{
getTypeMapping(tag).setValue(stmt, column++, entry.getValue());
}
else
{
stmt.setNull(column++, getDBTypes().get(i).getCode());
}
}
stmt.setInt(column++, index);
idHandler.setCDOID(stmt, column++, tag);
DBUtil.update(stmt, true);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
/**
* Move a list item from one position to another. Indices between both positions are updated so that the list remains
* consistent.
*
* @param accessor
* the accessor to use
* @param id
* the id of the revision in which to move the item
* @param oldPosition
* the old position of the item.
* @param newPosition
* the new position of the item.
*/
public void moveListItem(IDBStoreAccessor accessor, CDOID id, int oldPosition, int newPosition)
{
if (oldPosition == newPosition)
{
return;
}
// move element away temporarily
updateOneIndex(accessor, id, oldPosition, TEMP_INDEX);
// move elements in between
if (oldPosition < newPosition)
{
move1down(accessor, id, oldPosition, newPosition);
}
else
{
// oldPosition > newPosition -- equal case is handled above
move1up(accessor, id, newPosition, oldPosition);
}
// move temporary element to new position
updateOneIndex(accessor, id, TEMP_INDEX, newPosition);
}
private void updateOneIndex(IDBStoreAccessor accessor, CDOID id, int oldIndex, int newIndex)
{
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH);
stmt.setInt(1, newIndex);
idHandler.setCDOID(stmt, 2, id);
stmt.setInt(3, oldIndex);
DBUtil.update(stmt, true);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
/**
* Remove a list item from a specified a position.
*
* @param accessor
* the accessor to use
* @param id
* the id of the revision from which to remove the item
* @param index
* the index of the item to remove
*/
public void removeListItem(IDBStoreAccessor accessor, CDOID id, int index)
{
deleteItem(accessor, id, index);
move1down(accessor, id, index, UNBOUNDED_MOVE);
}
/**
* Move references downwards to close a gap at position <code>index</code>. Only indexes starting with
* <code>index + 1</code> and ending with <code>upperIndex</code> are moved down.
*/
private void move1down(IDBStoreAccessor accessor, CDOID id, int index, int upperIndex)
{
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(upperIndex == UNBOUNDED_MOVE ? sqlMoveDown : sqlMoveDownWithLimit,
ReuseProbability.HIGH);
idHandler.setCDOID(stmt, 1, id);
stmt.setInt(2, index);
if (upperIndex != UNBOUNDED_MOVE)
{
stmt.setInt(3, upperIndex);
}
DBUtil.update(stmt, false);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
/**
* Move references downwards to close a gap at position <code>index</code>. Only indexes starting with
* <code>index + 1</code> and ending with <code>upperIndex</code> are moved down.
*/
private void move1up(IDBStoreAccessor accessor, CDOID id, int index, int upperIndex)
{
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(upperIndex == UNBOUNDED_MOVE ? sqlMoveUp : sqlMoveUpWithLimit,
ReuseProbability.HIGH);
idHandler.setCDOID(stmt, 1, id);
stmt.setInt(2, index);
if (upperIndex != UNBOUNDED_MOVE)
{
stmt.setInt(3, upperIndex);
}
DBUtil.update(stmt, false);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
private void deleteItem(IDBStoreAccessor accessor, CDOID id, int index)
{
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(sqlDeleteItem, ReuseProbability.HIGH);
idHandler.setCDOID(stmt, 1, id);
stmt.setInt(2, index);
DBUtil.update(stmt, true);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
/**
* Set a value at a specified position to the given value.
*
* @param accessor
* the accessor to use
* @param id
* the id of the revision to set the value
* @param index
* the index of the item to set
* @param value
* the value to be set.
*/
public void setListItem(IDBStoreAccessor accessor, CDOID id, int index, Object value, long timestamp)
{
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
FeatureMap.Entry entry = (FeatureMap.Entry)value;
EStructuralFeature entryFeature = entry.getEStructuralFeature();
CDOID tag = getTagByFeature(entryFeature, timestamp);
String columnName = getColumnName(tag);
ITypeMapping mapping = getTypeMapping(tag);
try
{
stmt = statementCache.getPreparedStatement(sqlUpdateValue, ReuseProbability.HIGH);
idHandler.setCDOID(stmt, 1, tag);
int column = 2;
for (int i = 0; i < getColumnNames().size(); i++)
{
if (getColumnNames().get(i).equals(columnName))
{
mapping.setValue(stmt, column++, entry.getValue());
}
else
{
stmt.setNull(column++, getDBTypes().get(i).getCode());
}
}
idHandler.setCDOID(stmt, column++, id);
stmt.setInt(column++, index);
DBUtil.update(stmt, true);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
public void processDelta(final IDBStoreAccessor accessor, final CDOID id, final int branchId, int oldVersion,
final int newVersion, final long created, CDOListFeatureDelta listDelta)
{
CDOFeatureDeltaVisitor visitor = new CDOFeatureDeltaVisitor()
{
public void visit(CDOMoveFeatureDelta delta)
{
moveListItem(accessor, id, delta.getOldPosition(), delta.getNewPosition());
}
public void visit(CDOAddFeatureDelta delta)
{
insertListItem(accessor, id, delta.getIndex(), delta.getValue(), created);
}
public void visit(CDORemoveFeatureDelta delta)
{
removeListItem(accessor, id, delta.getIndex());
}
public void visit(CDOSetFeatureDelta delta)
{
setListItem(accessor, id, delta.getIndex(), delta.getValue(), created);
}
public void visit(CDOUnsetFeatureDelta delta)
{
if (delta.getFeature().isUnsettable())
{
throw new ImplementationError("Should not be called"); //$NON-NLS-1$
}
clearList(accessor, id);
}
public void visit(CDOListFeatureDelta delta)
{
throw new ImplementationError("Should not be called"); //$NON-NLS-1$
}
public void visit(CDOClearFeatureDelta delta)
{
clearList(accessor, id);
}
public void visit(CDOContainerFeatureDelta delta)
{
throw new ImplementationError("Should not be called"); //$NON-NLS-1$
}
};
for (CDOFeatureDelta delta : listDelta.getListChanges())
{
delta.accept(visitor);
}
}
}