blob: 3a3b6795a2da9259cd73d940621e59657676797a [file] [log] [blame]
/*
* Copyright (c) 2009-2013, 2015 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
* Stefan Winkler - major refactoring
* Stefan Winkler - 249610: [DB] Support external references (Implementation)
* 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.CDOBranch;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.branch.CDOBranchVersion;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.revision.CDOList;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.delta.CDOAddFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOContainerFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.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.CDORevisionDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta;
import org.eclipse.emf.cdo.eresource.EresourcePackage;
import org.eclipse.emf.cdo.server.IStoreAccessor.QueryXRefsContext;
import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
import org.eclipse.emf.cdo.server.db.IIDHandler;
import org.eclipse.emf.cdo.server.db.mapping.IClassMappingDeltaSupport;
import org.eclipse.emf.cdo.server.db.mapping.IListMappingDeltaSupport;
import org.eclipse.emf.cdo.server.db.mapping.ITypeMapping;
import org.eclipse.emf.cdo.server.internal.db.DBStore;
import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
import org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.NonAuditListTableMapping.NewListSizeResult;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBUtil;
import org.eclipse.net4j.db.IDBPreparedStatement;
import org.eclipse.net4j.db.IDBPreparedStatement.ReuseProbability;
import org.eclipse.net4j.db.ddl.IDBField;
import org.eclipse.net4j.util.ImplementationError;
import org.eclipse.net4j.util.collection.Pair;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import org.eclipse.net4j.util.om.monitor.OMMonitor.Async;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Eike Stepper
* @since 2.0
*/
public class HorizontalNonAuditClassMapping extends AbstractHorizontalClassMapping implements IClassMappingDeltaSupport
{
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, HorizontalNonAuditClassMapping.class);
private String sqlSelectAllObjectIDs;
private String sqlSelectCurrentAttributes;
private String sqlSelectCurrentVersion;
private String sqlInsertAttributes;
private String sqlUpdateAffix;
private String sqlUpdatePrefix;
private String sqlUpdateContainerPart;
private String sqlDelete;
private boolean hasLists;
private ThreadLocal<FeatureDeltaWriter> deltaWriter = new ThreadLocal<FeatureDeltaWriter>()
{
@Override
protected FeatureDeltaWriter initialValue()
{
return new FeatureDeltaWriter();
}
};
public HorizontalNonAuditClassMapping(AbstractHorizontalMappingStrategy mappingStrategy, EClass eClass)
{
super(mappingStrategy, eClass);
initSQLStrings();
hasLists = !getListMappings().isEmpty();
}
private void initSQLStrings()
{
// ----------- Select Revision ---------------------------
StringBuilder builder = new StringBuilder();
builder.append("SELECT "); //$NON-NLS-1$
builder.append(ATTRIBUTES_VERSION);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_CREATED);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_REVISED);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_RESOURCE);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_CONTAINER);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_FEATURE);
appendTypeMappingNames(builder, getValueMappings());
appendFieldNames(builder, getUnsettableFields());
appendFieldNames(builder, getListSizeFields());
builder.append(" FROM "); //$NON-NLS-1$
builder.append(getTable());
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(ATTRIBUTES_ID);
builder.append("=?"); //$NON-NLS-1$
sqlSelectCurrentAttributes = builder.toString();
// ----------- Select Version ---------------------------
builder = new StringBuilder();
builder.append("SELECT "); //$NON-NLS-1$
builder.append(ATTRIBUTES_VERSION);
builder.append(" FROM "); //$NON-NLS-1$
builder.append(getTable());
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(ATTRIBUTES_ID);
builder.append("=?"); //$NON-NLS-1$
sqlSelectCurrentVersion = builder.toString();
// ----------- Insert Attributes -------------------------
builder = new StringBuilder();
builder.append("INSERT INTO "); //$NON-NLS-1$
builder.append(getTable());
builder.append("("); //$NON-NLS-1$
builder.append(ATTRIBUTES_ID);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_VERSION);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_CREATED);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_REVISED);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_RESOURCE);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_CONTAINER);
builder.append(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_FEATURE);
appendTypeMappingNames(builder, getValueMappings());
appendFieldNames(builder, getUnsettableFields());
appendFieldNames(builder, getListSizeFields());
builder.append(") VALUES (?, ?, ?, ?, ?, ?, ?"); //$NON-NLS-1$
appendTypeMappingParameters(builder, getValueMappings());
appendFieldParameters(builder, getUnsettableFields());
appendFieldParameters(builder, getListSizeFields());
builder.append(")"); //$NON-NLS-1$
sqlInsertAttributes = builder.toString();
// ----------- Select all unrevised Object IDs ------
builder = new StringBuilder("SELECT "); //$NON-NLS-1$
builder.append(ATTRIBUTES_ID);
builder.append(" FROM "); //$NON-NLS-1$
builder.append(getTable());
sqlSelectAllObjectIDs = builder.toString();
// ----------- Update attributes --------------------
builder = new StringBuilder("UPDATE "); //$NON-NLS-1$
builder.append(getTable());
builder.append(" SET "); //$NON-NLS-1$
builder.append(ATTRIBUTES_VERSION);
builder.append("=? ,"); //$NON-NLS-1$
builder.append(ATTRIBUTES_CREATED);
builder.append("=? "); //$NON-NLS-1$
sqlUpdatePrefix = builder.toString();
builder = new StringBuilder(", "); //$NON-NLS-1$
builder.append(ATTRIBUTES_RESOURCE);
builder.append("=? ,"); //$NON-NLS-1$
builder.append(ATTRIBUTES_CONTAINER);
builder.append("=? ,"); //$NON-NLS-1$
builder.append(ATTRIBUTES_FEATURE);
builder.append("=? "); //$NON-NLS-1$
sqlUpdateContainerPart = builder.toString();
builder = new StringBuilder(" WHERE "); //$NON-NLS-1$
builder.append(ATTRIBUTES_ID);
builder.append("=? "); //$NON-NLS-1$
sqlUpdateAffix = builder.toString();
builder = new StringBuilder("DELETE FROM "); //$NON-NLS-1$
builder.append(getTable());
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(ATTRIBUTES_ID);
builder.append("=? "); //$NON-NLS-1$
sqlDelete = builder.toString();
}
@Override
protected void writeValues(IDBStoreAccessor accessor, InternalCDORevision revision)
{
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlInsertAttributes, ReuseProbability.HIGH);
try
{
int column = 1;
idHandler.setCDOID(stmt, column++, revision.getID());
stmt.setInt(column++, revision.getVersion());
stmt.setLong(column++, revision.getTimeStamp());
stmt.setLong(column++, revision.getRevised());
idHandler.setCDOID(stmt, column++, revision.getResourceID());
idHandler.setCDOID(stmt, column++, (CDOID)revision.getContainerID());
stmt.setInt(column++, revision.getContainingFeatureID());
int isSetCol = column + getValueMappings().size();
for (ITypeMapping mapping : getValueMappings())
{
EStructuralFeature feature = mapping.getFeature();
if (feature.isUnsettable())
{
if (revision.getValue(feature) == null)
{
stmt.setBoolean(isSetCol++, false);
// also set value column to default value
mapping.setDefaultValue(stmt, column++);
continue;
}
stmt.setBoolean(isSetCol++, true);
}
mapping.setValueFromRevision(stmt, column++, revision);
}
Map<EStructuralFeature, IDBField> listSizeFields = getListSizeFields();
if (listSizeFields != null)
{
// isSetCol now points to the first listTableSize-column
column = isSetCol;
for (EStructuralFeature feature : listSizeFields.keySet())
{
CDOList list = revision.getList(feature);
stmt.setInt(column++, list.size());
}
}
DBUtil.update(stmt, true);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
DBUtil.close(stmt);
}
}
public IDBPreparedStatement createObjectIDStatement(IDBStoreAccessor accessor)
{
if (TRACER.isEnabled())
{
TRACER.format("Created ObjectID Statement : {0}", sqlSelectAllObjectIDs); //$NON-NLS-1$
}
return accessor.getDBConnection().prepareStatement(sqlSelectAllObjectIDs, ReuseProbability.HIGH);
}
public IDBPreparedStatement createResourceQueryStatement(IDBStoreAccessor accessor, CDOID folderId, String name,
boolean exactMatch, CDOBranchPoint branchPoint)
{
long timeStamp = branchPoint.getTimeStamp();
if (timeStamp != CDORevision.UNSPECIFIED_DATE)
{
throw new IllegalArgumentException("Non-audit store does not support explicit timeStamp in resource query"); //$NON-NLS-1$
}
EStructuralFeature nameFeature = EresourcePackage.eINSTANCE.getCDOResourceNode_Name();
ITypeMapping nameValueMapping = getValueMapping(nameFeature);
if (nameValueMapping == null)
{
throw new ImplementationError(nameFeature + " not found in ClassMapping " + this); //$NON-NLS-1$
}
StringBuilder builder = new StringBuilder();
builder.append("SELECT "); //$NON-NLS-1$
builder.append(ATTRIBUTES_ID);
builder.append(" FROM "); //$NON-NLS-1$
builder.append(getTable());
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(ATTRIBUTES_VERSION);
builder.append(">0 AND "); //$NON-NLS-1$
builder.append(ATTRIBUTES_CONTAINER);
builder.append("=? AND "); //$NON-NLS-1$
builder.append(nameValueMapping.getField());
if (name == null)
{
builder.append(" IS NULL"); //$NON-NLS-1$
}
else
{
builder.append(exactMatch ? "=? " : " LIKE ? "); //$NON-NLS-1$ //$NON-NLS-2$
}
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(builder.toString(),
ReuseProbability.MEDIUM);
try
{
int column = 1;
idHandler.setCDOID(stmt, column++, folderId);
if (name != null)
{
String queryName = exactMatch ? name : name + "%"; //$NON-NLS-1$
nameValueMapping.setValue(stmt, column++, queryName);
}
if (TRACER.isEnabled())
{
TRACER.format("Created Resource Query: {0}", stmt.toString()); //$NON-NLS-1$
}
return stmt;
}
catch (SQLException ex)
{
DBUtil.close(stmt); // only release on error
throw new DBException(ex);
}
}
public boolean readRevision(IDBStoreAccessor accessor, InternalCDORevision revision, int listChunk)
{
long timeStamp = revision.getTimeStamp();
if (timeStamp != CDOBranchPoint.UNSPECIFIED_DATE)
{
throw new UnsupportedOperationException("Mapping strategy does not support audits"); //$NON-NLS-1$
}
CDOID id = revision.getID();
DBStore store = (DBStore)getMappingStrategy().getStore();
IIDHandler idHandler = store.getIDHandler();
IDBPreparedStatement stmtVersion = null;
IDBPreparedStatement stmtAttributes = accessor.getDBConnection().prepareStatement(sqlSelectCurrentAttributes,
ReuseProbability.HIGH);
try
{
if (hasLists)
{
stmtVersion = accessor.getDBConnection().prepareStatement(sqlSelectCurrentVersion, ReuseProbability.HIGH);
stmtVersion.setMaxRows(1); // Optimization: only 1 row
idHandler.setCDOID(stmtVersion, 1, id);
}
idHandler.setCDOID(stmtAttributes, 1, id);
for (;;)
{
// Read singleval-attribute table always (even without modeled attributes!)
boolean success = readValuesFromStatement(stmtAttributes, revision, accessor);
if (hasLists)
{
// Read multival tables only if revision exists
if (success)
{
int currentVersion;
try
{
readLists(accessor, revision, listChunk);
currentVersion = readVersion(stmtVersion);
}
catch (IndexOutOfBoundsException ex)
{
// A commit has appended list rows after the list size has been read in readValuesFromStatement().
// Trigger start from scratch below.
currentVersion = CDOBranchVersion.UNSPECIFIED_VERSION;
}
if (currentVersion != revision.getVersion())
{
// A commit has changed the revision while reading the lists. Start from scratch!
revision.clearValues(); // Make sure that lists are recreated
continue;
}
}
}
return success;
}
}
catch (SQLException ex)
{
throw new DBException(ex);
}
finally
{
DBUtil.close(stmtAttributes);
DBUtil.close(stmtVersion);
}
}
private int readVersion(IDBPreparedStatement stmt)
{
ResultSet resultSet = null;
try
{
resultSet = stmt.executeQuery();
if (resultSet.next())
{
return resultSet.getInt(1);
}
return CDOBranchVersion.UNSPECIFIED_VERSION;
}
catch (SQLException ex)
{
throw new DBException(ex);
}
finally
{
DBUtil.close(resultSet);
}
}
@Override
protected void detachAttributes(IDBStoreAccessor accessor, CDOID id, int version, CDOBranch branch, long timeStamp,
OMMonitor monitor)
{
rawDelete(accessor, id, version, branch, monitor);
AbstractHorizontalMappingStrategy mappingStrategy = (AbstractHorizontalMappingStrategy)getMappingStrategy();
mappingStrategy.removeObjectType(accessor, id);
}
@Override
protected void rawDeleteAttributes(IDBStoreAccessor accessor, CDOID id, CDOBranch branch, int version,
OMMonitor monitor)
{
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlDelete, ReuseProbability.HIGH);
try
{
idHandler.setCDOID(stmt, 1, id);
DBUtil.update(stmt, true);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
DBUtil.close(stmt);
}
}
public void writeRevisionDelta(IDBStoreAccessor accessor, InternalCDORevisionDelta delta, long created,
OMMonitor monitor)
{
Async async = null;
monitor.begin();
try
{
try
{
async = monitor.forkAsync();
FeatureDeltaWriter writer = deltaWriter.get();
writer.process(accessor, delta, created);
}
finally
{
if (async != null)
{
async.stop();
}
}
}
finally
{
monitor.done();
}
}
/**
* @author Eike Stepper
*/
private class FeatureDeltaWriter implements CDOFeatureDeltaVisitor
{
private CDOID id;
private int oldVersion;
private long created;
private IDBStoreAccessor accessor;
private boolean updateContainer;
private List<Pair<ITypeMapping, Object>> attributeChanges;
private List<Pair<EStructuralFeature, Integer>> listSizeChanges;
private int newContainingFeatureID;
private CDOID newContainerID;
private CDOID newResourceID;
private int branchId;
private int newVersion;
public FeatureDeltaWriter()
{
attributeChanges = new ArrayList<Pair<ITypeMapping, Object>>();
listSizeChanges = new ArrayList<Pair<EStructuralFeature, Integer>>();
}
protected void reset()
{
attributeChanges.clear();
listSizeChanges.clear();
updateContainer = false;
}
public void process(IDBStoreAccessor accessor, CDORevisionDelta delta, long created)
{
try
{
// Set context
id = delta.getID();
branchId = delta.getBranch().getID();
oldVersion = delta.getVersion();
newVersion = oldVersion + 1;
this.created = created;
this.accessor = accessor;
// Process revision delta tree
delta.accept(this);
updateAttributes();
}
finally
{
// Clean up
reset();
}
}
public void visit(CDOMoveFeatureDelta delta)
{
throw new ImplementationError("Should not be called"); //$NON-NLS-1$
}
public void visit(CDOSetFeatureDelta delta)
{
if (delta.getFeature().isMany())
{
throw new ImplementationError("Should not be called"); //$NON-NLS-1$
}
ITypeMapping am = getValueMapping(delta.getFeature());
if (am == null)
{
throw new IllegalArgumentException("AttributeMapping for " + delta.getFeature() + " is null!"); //$NON-NLS-1$ //$NON-NLS-2$
}
attributeChanges.add(Pair.create(am, delta.getValue()));
}
public void visit(CDOUnsetFeatureDelta delta)
{
// TODO: correct this when DBStore implements unsettable features
// see Bugs 259868 and 263010
ITypeMapping tm = getValueMapping(delta.getFeature());
attributeChanges.add(Pair.create(tm, null));
}
public void visit(CDOListFeatureDelta delta)
{
EStructuralFeature feature = delta.getFeature();
int oldSize = delta.getOriginSize();
int newSize = -1;
try
{
IListMappingDeltaSupport listMapping = (IListMappingDeltaSupport)getListMapping(feature);
listMapping.processDelta(accessor, id, branchId, oldVersion, oldVersion + 1, created, delta);
}
catch (NewListSizeResult result)
{
newSize = result.getNewListSize();
}
if (oldSize != newSize)
{
listSizeChanges.add(Pair.create(feature, newSize));
}
}
public void visit(CDOClearFeatureDelta delta)
{
throw new ImplementationError("Should not be called"); //$NON-NLS-1$
}
public void visit(CDOAddFeatureDelta delta)
{
throw new ImplementationError("Should not be called"); //$NON-NLS-1$
}
public void visit(CDORemoveFeatureDelta delta)
{
throw new ImplementationError("Should not be called"); //$NON-NLS-1$
}
public void visit(CDOContainerFeatureDelta delta)
{
newContainingFeatureID = delta.getContainerFeatureID();
newContainerID = (CDOID)delta.getContainerID();
newResourceID = delta.getResourceID();
updateContainer = true;
}
private void updateAttributes()
{
IIDHandler idHandler = getMappingStrategy().getStore().getIDHandler();
String sql = buildUpdateSQL();
IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sql, ReuseProbability.MEDIUM);
try
{
int column = 1;
stmt.setInt(column++, newVersion);
stmt.setLong(column++, created);
if (updateContainer)
{
idHandler.setCDOID(stmt, column++, newResourceID, created);
idHandler.setCDOID(stmt, column++, newContainerID, created);
stmt.setInt(column++, newContainingFeatureID);
}
column = setUpdateAttributeValues(attributeChanges, stmt, column);
column = setUpdateListSizeChanges(listSizeChanges, stmt, column);
idHandler.setCDOID(stmt, column++, id);
DBUtil.update(stmt, true);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
DBUtil.close(stmt);
}
}
private String buildUpdateSQL()
{
StringBuilder builder = new StringBuilder(sqlUpdatePrefix);
if (updateContainer)
{
builder.append(sqlUpdateContainerPart);
}
for (Pair<ITypeMapping, Object> change : attributeChanges)
{
builder.append(", "); //$NON-NLS-1$
ITypeMapping typeMapping = change.getElement1();
builder.append(typeMapping.getField());
builder.append("=?"); //$NON-NLS-1$
if (typeMapping.getFeature().isUnsettable())
{
builder.append(", "); //$NON-NLS-1$
builder.append(getUnsettableFields().get(typeMapping.getFeature()));
builder.append("=?"); //$NON-NLS-1$
}
}
for (Pair<EStructuralFeature, Integer> change : listSizeChanges)
{
builder.append(", "); //$NON-NLS-1$
EStructuralFeature feature = change.getElement1();
builder.append(getListSizeFields().get(feature));
builder.append("=?"); //$NON-NLS-1$
}
builder.append(sqlUpdateAffix);
return builder.toString();
}
private int setUpdateAttributeValues(List<Pair<ITypeMapping, Object>> attributeChanges, IDBPreparedStatement stmt,
int col) throws SQLException
{
for (Pair<ITypeMapping, Object> change : attributeChanges)
{
ITypeMapping typeMapping = change.getElement1();
Object value = change.getElement2();
if (typeMapping.getFeature().isUnsettable())
{
// feature is unsettable
if (value == null)
{
// feature is unset
typeMapping.setDefaultValue(stmt, col++);
stmt.setBoolean(col++, false);
}
else
{
// feature is set
typeMapping.setValue(stmt, col++, value);
stmt.setBoolean(col++, true);
}
}
else
{
typeMapping.setValue(stmt, col++, change.getElement2());
}
}
return col;
}
private int setUpdateListSizeChanges(List<Pair<EStructuralFeature, Integer>> attributeChanges,
IDBPreparedStatement stmt, int col) throws SQLException
{
for (Pair<EStructuralFeature, Integer> change : listSizeChanges)
{
stmt.setInt(col++, change.getElement2());
}
return col;
}
}
@Override
protected void reviseOldRevision(IDBStoreAccessor accessor, CDOID id, CDOBranch branch, long timeStamp)
{
// do nothing
}
@Override
protected String getListXRefsWhere(QueryXRefsContext context)
{
if (CDORevision.UNSPECIFIED_DATE != context.getTimeStamp())
{
throw new IllegalArgumentException("Non-audit mode does not support timestamp specification");
}
if (!context.getBranch().isMainBranch())
{
throw new IllegalArgumentException("Non-audit mode does not support branch specification");
}
return ATTRIBUTES_REVISED + "=0";
}
}