diff options
author | Stefan Winkler | 2009-07-27 13:35:59 +0000 |
---|---|---|
committer | Stefan Winkler | 2009-07-27 13:35:59 +0000 |
commit | fb197e566f20eed70274be006d985711c77bf7b7 (patch) | |
tree | 0c462e9ac98f437a691fdb0913521119c1730779 | |
parent | e063f13b8575e584c2f9efd698af34d0bd6f7360 (diff) | |
download | cdo-fb197e566f20eed70274be006d985711c77bf7b7.tar.gz cdo-fb197e566f20eed70274be006d985711c77bf7b7.tar.xz cdo-fb197e566f20eed70274be006d985711c77bf7b7.zip |
[254455] [DB] Support FeatureMaps
https://bugs.eclipse.org/bugs/show_bug.cgi?id=254455
14 files changed, 1670 insertions, 8 deletions
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/ITypeMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/ITypeMapping.java index 1930e2db23..736d5d1761 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/ITypeMapping.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/ITypeMapping.java @@ -8,11 +8,13 @@ * Contributors: * Eike Stepper - initial API and implementation * Stefan Winkler - major refactoring + * Christopher Albert - 254455: [DB] Support FeatureMaps https://bugs.eclipse.org/bugs/show_bug.cgi?id=254455 */ package org.eclipse.emf.cdo.server.db.mapping; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; +import org.eclipse.net4j.db.DBType; import org.eclipse.net4j.db.ddl.IDBField; import org.eclipse.net4j.db.ddl.IDBTable; @@ -42,6 +44,12 @@ public interface ITypeMapping public IDBField getField(); /** + * @return The db type which is associated with this mapping. + * @since 3.0 + */ + public DBType getDBType(); + + /** * Creates the DBField and adds it to the given table. The name of the DBField is derived from the feature. * * @param table @@ -61,6 +69,17 @@ public interface ITypeMapping public void createDBField(IDBTable table, String fieldName); /** + * Sets the DBField. The name of the DBField is explicitly determined by the corresponding parameter. + * + * @param table + * the table to add this field to. + * @param fieldName + * the name for the DBField. + * @since 3.0 + */ + public void setDBField(IDBTable table, String fieldName); + + /** * Set the given value to the JDBC {@link PreparedStatement} using an appropriate <code>setXxx</code> method. * * @param stmt diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/CDODBSchema.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/CDODBSchema.java index 07b8b3e9aa..129e1e4f48 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/CDODBSchema.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/CDODBSchema.java @@ -152,6 +152,19 @@ public class CDODBSchema extends DBSchema public static final String LIST_VALUE = "cdo_value"; //$NON-NLS-1$ + /** + * Field names of featuremap tables + */ + public static final String FEATUREMAP_REVISION_ID = "cdo_id"; //$NON-NLS-1$ + + public static final String FEATUREMAP_VERSION = "cdo_version"; //$NON-NLS-1$ + + public static final String FEATUREMAP_IDX = "cdo_idx"; //$NON-NLS-1$ + + public static final String FEATUREMAP_TAG = "cdo_tag"; //$NON-NLS-1$ + + public static final String FEATUREMAP_VALUE = "cdo_value"; //$NON-NLS-1$ + private CDODBSchema() { super("CDO"); //$NON-NLS-1$ diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/AbstractMappingStrategy.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/AbstractMappingStrategy.java index 460bfff42e..7db4fe3363 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/AbstractMappingStrategy.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/AbstractMappingStrategy.java @@ -44,6 +44,7 @@ import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.util.FeatureMapUtil; import java.sql.Connection; import java.sql.PreparedStatement; @@ -395,5 +396,14 @@ public abstract class AbstractMappingStrategy extends Lifecycle implements IMapp return mapping; } + public final IListMapping createFeatureMapMapping(EClass containingClass, EStructuralFeature feature) + { + checkArg(FeatureMapUtil.isFeatureMap(feature), "Only FeatureMaps allowed."); //$NON-NLS-1$ + IListMapping mapping = doCreateFeatureMapMapping(containingClass, feature); + return mapping; + } + public abstract IListMapping doCreateListMapping(EClass containingClass, EStructuralFeature feature); + + public abstract IListMapping doCreateFeatureMapMapping(EClass containingClass, EStructuralFeature feature); } diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/TypeMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/TypeMapping.java index afd8e4bea0..fba14c74fe 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/TypeMapping.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/TypeMapping.java @@ -112,6 +112,11 @@ public abstract class TypeMapping implements ITypeMapping field = table.addField(fieldName, fieldType, fieldLength); } + public final void setDBField(IDBTable table, String fieldName) + { + field = table.getField(fieldName); + } + public final IDBField getField() { return field; @@ -156,7 +161,7 @@ public abstract class TypeMapping implements ITypeMapping return getDBType().getCode(); } - protected DBType getDBType() + public DBType getDBType() { return dbType; } diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/TypeMappingFactory.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/TypeMappingFactory.java index 4f47e9e6c3..5f0bdd7608 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/TypeMappingFactory.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/TypeMappingFactory.java @@ -26,8 +26,11 @@ import org.eclipse.emf.ecore.EEnum; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.EcorePackage; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; /** * @author Stefan Winkler @@ -173,6 +176,8 @@ public enum TypeMappingFactory private static Map<Pair<CDOType, DBType>, TypeMappingFactory> mappingTable = new HashMap<Pair<CDOType, DBType>, TypeMappingFactory>(); + private static Set<DBType> defaultFeatureMapDBTypes; + static { /* --- initialize default types --- */ @@ -237,8 +242,7 @@ public enum TypeMappingFactory mappingTable.put(new Pair<CDOType, DBType>(CDOType.STRING, DBType.VARCHAR), STRING_MAPPING); mappingTable.put(new Pair<CDOType, DBType>(CDOType.STRING, DBType.CLOB), STRING_MAPPING); - // preliminary - mappingTable.put(new Pair<CDOType, DBType>(CDOType.FEATURE_MAP_ENTRY, DBType.VARCHAR), STRING_MAPPING); + defaultFeatureMapDBTypes = new HashSet<DBType>(defaultTypeMap.values()); } protected abstract ITypeMapping doCreateTypeMapping(IMappingStrategy mappingStrategy, EStructuralFeature feature, @@ -298,4 +302,9 @@ public enum TypeMappingFactory return DBType.VARCHAR; } + + public static Collection<DBType> getDefaultFeatureMapDBTypes() + { + return defaultFeatureMapDBTypes; + } } diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractFeatureMapTableMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractFeatureMapTableMapping.java new file mode 100644 index 0000000000..a9edb800c8 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractFeatureMapTableMapping.java @@ -0,0 +1,716 @@ +/** + * Copyright (c) 2004 - 2009 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 https://bugs.eclipse.org/bugs/show_bug.cgi?id=271444 + * Christopher Albert - 254455: [DB] Support FeatureMaps https://bugs.eclipse.org/bugs/show_bug.cgi?id=254455 + */ +package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal; + +import org.eclipse.emf.cdo.common.revision.CDOList; +import org.eclipse.emf.cdo.common.revision.CDORevision; +import org.eclipse.emf.cdo.common.revision.CDORevisionUtil; +import org.eclipse.emf.cdo.server.IStoreChunkReader.Chunk; +import org.eclipse.emf.cdo.server.db.CDODBUtil; +import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; +import org.eclipse.emf.cdo.server.db.IDBStoreChunkReader; +import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability; +import org.eclipse.emf.cdo.server.db.mapping.IListMapping; +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.emf.cdo.server.internal.db.bundle.OM; +import org.eclipse.emf.cdo.server.internal.db.mapping.TypeMapping; +import org.eclipse.emf.cdo.server.internal.db.mapping.TypeMappingFactory; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDOList; +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.db.ddl.IDBField; +import org.eclipse.net4j.db.ddl.IDBTable; +import org.eclipse.net4j.db.ddl.IDBIndex.Type; +import org.eclipse.net4j.util.collection.MoveableList; +import org.eclipse.net4j.util.om.trace.ContextTracer; + +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.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * This abstract base class provides basic behavior needed for mapping many-valued attributes to tables. + * + * @author Eike Stepper + * @since 3.0 + */ +public abstract class AbstractFeatureMapTableMapping implements IListMapping +{ + private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, AbstractFeatureMapTableMapping.class); + + /** + * The feature for this mapping. + */ + private EStructuralFeature feature; + + /** + * The table of this mapping. + */ + private IDBTable table; + + /** + * The tags mapped to column names + */ + private HashMap<Long, String> tagMap; + + /** + * Column name Set + */ + private List<String> columnNames; + + /** + * The type mappings for the value fields. + */ + private Map<Long, ITypeMapping> typeMappings; + + /** + * The associated mapping strategy. + */ + private IMappingStrategy mappingStrategy; + + // --------- SQL strings - see initSqlStrings() ----------------- + private String sqlSelectChunksPrefix; + + private String sqlOrderByIndex; + + protected String sqlInsert; + + private EClass containingClass; + + private String sqlGetListLastIndex; + + private List<DBType> dbTypes; + + public AbstractFeatureMapTableMapping(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature) + { + this.mappingStrategy = mappingStrategy; + this.feature = feature; + containingClass = eClass; + + initDBTypes(); + initTable(); + initSqlStrings(); + } + + private void initDBTypes() + { + // TODO add annotation processing here ... + + dbTypes = new ArrayList<DBType>(TypeMappingFactory.getDefaultFeatureMapDBTypes()); + } + + private void initTable() + { + String tableName = mappingStrategy.getTableName(containingClass, feature); + table = mappingStrategy.getStore().getDBSchema().addTable(tableName); + + // add fields for keys (cdo_id, version, feature_id) + FieldInfo[] fields = getKeyFields(); + IDBField[] dbFields = new IDBField[fields.length]; + + for (int i = 0; i < fields.length; i++) + { + dbFields[i] = table.addField(fields[i].getName(), fields[i].getDbType()); + } + + // add field for list index + IDBField idxField = table.addField(CDODBSchema.FEATUREMAP_IDX, DBType.INTEGER); + + // add field for FeatureMap tag (MetaID for Feature in CDO registry) + IDBField tagField = table.addField(CDODBSchema.FEATUREMAP_TAG, DBType.INTEGER); + + tagMap = new HashMap<Long, String>(); + typeMappings = new HashMap<Long, ITypeMapping>(); + columnNames = new ArrayList<String>(); + + // create columns for all DBTypes + for (DBType type : getDBTypes()) + { + String column = CDODBSchema.FEATUREMAP_VALUE + "_" + type.name(); + table.addField(column, type); + columnNames.add(column); + } + + table.addIndex(Type.NON_UNIQUE, dbFields); + table.addIndex(Type.NON_UNIQUE, idxField); + table.addIndex(Type.NON_UNIQUE, tagField); + } + + protected abstract FieldInfo[] getKeyFields(); + + protected abstract void setKeyFields(PreparedStatement stmt, CDORevision revision) throws SQLException; + + public Collection<IDBTable> getDBTables() + { + return Arrays.asList(table); + } + + private void initSqlStrings() + { + String tableName = getTable().getName(); + FieldInfo[] fields = getKeyFields(); + + // ---------------- SELECT to read chunks ---------------------------- + StringBuilder builder = new StringBuilder(); + builder.append("SELECT "); + + builder.append(CDODBSchema.FEATUREMAP_TAG); + builder.append(", "); + + Iterator<String> iter = columnNames.iterator(); + while (iter.hasNext()) + { + builder.append(iter.next()); + if (iter.hasNext()) + { + builder.append(", "); + } + } + + builder.append(" FROM "); + builder.append(tableName); + builder.append(" WHERE "); + + for (int i = 0; i < fields.length; i++) + { + builder.append(fields[i].getName()); + if (i + 1 < fields.length) + { + // more to come + builder.append("= ? AND "); + } + else + { + // last one + builder.append("= ? "); + } + } + + sqlSelectChunksPrefix = builder.toString(); + + sqlOrderByIndex = " ORDER BY " + CDODBSchema.FEATUREMAP_IDX; //$NON-NLS-1$ + + // ----------------- count list size -------------------------- + + builder = new StringBuilder("SELECT max("); + builder.append(CDODBSchema.FEATUREMAP_IDX); + builder.append(") FROM "); + builder.append(tableName); + builder.append(" WHERE "); + + for (int i = 0; i < fields.length; i++) + { + builder.append(fields[i].getName()); + if (i + 1 < fields.length) + { + // more to come + builder.append("= ? AND "); + } + else + { + // last one + builder.append("= ? "); + } + } + + sqlGetListLastIndex = builder.toString(); + + // INSERT with dynamic field name + // TODO: Better: universal INSERT-Statement, because of stmt caching! + + // ----------------- INSERT - prefix ----------------- + builder = new StringBuilder("INSERT INTO "); + builder.append(tableName); + builder.append(" ("); //$NON-NLS-1$ + for (int i = 0; i < fields.length; i++) + { + builder.append(fields[i].getName()); + builder.append(", "); //$NON-NLS-1$ + } + for (int i = 0; i < columnNames.size(); i++) + { + builder.append(columnNames.get(i)); + builder.append(", "); //$NON-NLS-1$ + } + builder.append(CDODBSchema.FEATUREMAP_IDX); + builder.append(", "); //$NON-NLS-1$ + builder.append(CDODBSchema.FEATUREMAP_TAG); + builder.append(") VALUES ("); //$NON-NLS-1$ + for (int i = 0; i < fields.length + columnNames.size(); i++) + { + builder.append("?, "); + } + builder.append("?, ?)"); + + sqlInsert = builder.toString(); + } + + public final EStructuralFeature getFeature() + { + return feature; + } + + protected List<DBType> getDBTypes() + { + return dbTypes; + } + + public final EClass getContainingClass() + { + return containingClass; + } + + protected final IDBTable getTable() + { + return table; + } + + protected final List<String> getColumnNames() + { + return columnNames; + } + + protected final Map<Long, ITypeMapping> getTypeMappings() + { + return typeMappings; + } + + protected final Map<Long, String> getTagMap() + { + return tagMap; + } + + public void readValues(IDBStoreAccessor accessor, InternalCDORevision revision, int listChunk) + { + MoveableList<Object> list = revision.getList(getFeature()); + int listSize = -1; + + if (listChunk != CDORevision.UNCHUNKED) + { + listSize = getListLastIndex(accessor, revision); + if (listSize == -1) + { + // list is empty - take shortcut + return; + } + else + { + // subtract amount of items we are going to read now + listSize -= listChunk; + } + } + + if (TRACER.isEnabled()) + { + TRACER.format("Reading list values for feature {0}.{1} of {2}v{3}", containingClass.getName(), feature.getName(), + revision.getID(), revision.getVersion()); + } + + PreparedStatement pstmt = null; + ResultSet resultSet = null; + + try + { + String sql = sqlSelectChunksPrefix + sqlOrderByIndex; + + pstmt = accessor.getStatementCache().getPreparedStatement(sql, ReuseProbability.HIGH); + + setKeyFields(pstmt, revision); + + // if (TRACER.isEnabled()) + // { + // TRACER.trace(pstmt.toString()); + // } + + if (listChunk != CDORevision.UNCHUNKED) + { + pstmt.setMaxRows(listChunk); // optimization - don't read unneeded rows. + } + + resultSet = pstmt.executeQuery(); + + while ((listChunk == CDORevision.UNCHUNKED || --listChunk >= 0) && resultSet.next()) + { + Long tag = resultSet.getLong(1); + String column = getColumnName(tag); + int fieldIndex = resultSet.findColumn(column); + Object value = getTypeMapping(tag).readValue(resultSet, fieldIndex); + + if (TRACER.isEnabled()) + { + TRACER.format("Read value for index {0} from result set: {1}", list.size(), value); + } + + list.add(CDORevisionUtil.createFeatureMapEntry(getFeatureByTag(tag), value)); + } + + while (listSize-- >= 0) + { + if (TRACER.isEnabled()) + { + TRACER.format("Adding UNINITIALIZED for index {0} ", list.size()); + } + + list.add(InternalCDOList.UNINITIALIZED); + } + } + catch (SQLException ex) + { + throw new DBException(ex); + } + finally + { + DBUtil.close(resultSet); + accessor.getStatementCache().releasePreparedStatement(pstmt); + } + + if (TRACER.isEnabled()) + { + TRACER.format("Reading list values done for feature {0}.{1} of {2}v{3}", containingClass.getName(), feature //$NON-NLS-1$ + .getName(), revision.getID(), revision.getVersion()); + } + } + + private void addFeature(Long tag) + { + EStructuralFeature modelFeature = getFeatureByTag(tag); + + TypeMapping typeMapping = (TypeMapping)mappingStrategy.createValueMapping(modelFeature); + String column = CDODBSchema.FEATUREMAP_VALUE + "_" + typeMapping.getDBType(); + + tagMap.put(tag, column); + typeMapping.setDBField(table, column); + typeMappings.put(tag, typeMapping); + } + + /** + * Return the last (maximum) list index. (euals to size-1) + * + * @param accessor + * the accessor to use + * @param revision + * the revision to which the feature list belongs + * @return the last index or <code>-1</code> if the list is empty. + */ + private int getListLastIndex(IDBStoreAccessor accessor, InternalCDORevision revision) + { + PreparedStatement pstmt = null; + ResultSet resultSet = null; + + try + { + pstmt = accessor.getStatementCache().getPreparedStatement(sqlGetListLastIndex, ReuseProbability.HIGH); + + setKeyFields(pstmt, revision); + + // if (TRACER.isEnabled()) + // { + // TRACER.trace(pstmt.toString()); + // } + + resultSet = pstmt.executeQuery(); + + if (!resultSet.next()) + { + if (TRACER.isEnabled()) + { + TRACER.trace("No last index found -> list is empty. "); + } + + return -1; + } + else + { + int result = resultSet.getInt(1); + if (TRACER.isEnabled()) + { + TRACER.trace("Read list last index = " + result); + } + + return result; + } + } + catch (SQLException ex) + { + throw new DBException(ex); + } + finally + { + DBUtil.close(resultSet); + accessor.getStatementCache().releasePreparedStatement(pstmt); + } + } + + public final void readChunks(IDBStoreChunkReader chunkReader, List<Chunk> chunks, String where) + { + if (TRACER.isEnabled()) + { + TRACER.format("Reading list chunk values for feature {0}.{1} of {2}v{3}", containingClass.getName(), feature //$NON-NLS-1$ + .getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); + } + + PreparedStatement pstmt = null; + ResultSet resultSet = null; + + try + { + StringBuilder builder = new StringBuilder(sqlSelectChunksPrefix); + if (where != null) + { + builder.append(where); + } + + builder.append(sqlOrderByIndex); + + String sql = builder.toString(); + pstmt = chunkReader.getAccessor().getStatementCache().getPreparedStatement(sql, ReuseProbability.LOW); + setKeyFields(pstmt, chunkReader.getRevision()); + // if (TRACER.isEnabled()) + // { + // TRACER.trace(pstmt.toString()); + // } + + resultSet = pstmt.executeQuery(); + + Chunk chunk = null; + int chunkSize = 0; + int chunkIndex = 0; + int indexInChunk = 0; + + while (resultSet.next()) + { + Long tag = resultSet.getLong(1); + String column = getColumnName(tag); + int fieldIndex = resultSet.findColumn(column); + Object value = getTypeMapping(tag).readValue(resultSet, fieldIndex); + + if (chunk == null) + { + chunk = chunks.get(chunkIndex++); + chunkSize = chunk.size(); + + if (TRACER.isEnabled()) + { + TRACER.format("Current chunk no. {0} is [start = {1}, size = {2}]", chunkIndex - 1, chunk.getStartIndex(), + chunkSize); + } + } + + if (TRACER.isEnabled()) + { + TRACER.format("Read value for chunk index {0} from result set: {1}", indexInChunk, value); + } + + chunk.add(indexInChunk++, CDORevisionUtil.createFeatureMapEntry(getFeatureByTag(tag), value)); + if (indexInChunk == chunkSize) + { + if (TRACER.isEnabled()) + { + TRACER.format("Chunk finished."); + } + + chunk = null; + indexInChunk = 0; + } + } + + if (TRACER.isEnabled()) + { + TRACER.format("Reading list chunk values done for feature {0}.{1} of {2}v{3}", containingClass.getName(), + getTagByFeature(feature), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion()); + } + } + catch (SQLException ex) + { + throw new DBException(ex); + } + finally + { + DBUtil.close(resultSet); + chunkReader.getAccessor().getStatementCache().releasePreparedStatement(pstmt); + } + } + + public void writeValues(IDBStoreAccessor accessor, InternalCDORevision revision) + { + CDOList values = revision.getList(getFeature()); + + int idx = 0; + for (Object element : values) + { + writeValue(accessor, revision, idx++, element); + } + } + + protected final void writeValue(IDBStoreAccessor accessor, CDORevision revision, int idx, Object value) + { + PreparedStatement stmt = null; + + if (TRACER.isEnabled()) + { + TRACER + .format( + "Writing value for feature {0}.{1} index {2} of {3}v{4} : {5}", containingClass.getName(), getTagByFeature(feature), idx, revision.getID(), revision.getVersion(), value); //$NON-NLS-1$ + } + + try + { + FeatureMap.Entry entry = (FeatureMap.Entry)value; + EStructuralFeature entryFeature = entry.getEStructuralFeature(); + Long tag = getTagByFeature(entryFeature); + String column = getColumnName(tag); + + String sql = sqlInsert; + + stmt = accessor.getStatementCache().getPreparedStatement(sql, ReuseProbability.HIGH); + + setKeyFields(stmt, revision); + int stmtIndex = getKeyFields().length + 1; + + for (int i = 0; i < columnNames.size(); i++) + { + if (columnNames.get(i).equals(column)) + { + getTypeMapping(tag).setValue(stmt, stmtIndex++, entry.getValue()); + } + else + { + stmt.setNull(stmtIndex++, getDBTypes().get(i).getCode()); + } + } + + stmt.setInt(stmtIndex++, idx); + stmt.setLong(stmtIndex++, tag); + + CDODBUtil.sqlUpdate(stmt, true); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().releasePreparedStatement(stmt); + } + } + + /** + * Get column name (lazy) + * + * @param tag + * The feature's MetaID in CDO + * @return the column name where the values are stored + */ + + protected String getColumnName(Long tag) + { + String column = tagMap.get(tag); + if (column == null) + { + addFeature(tag); + column = tagMap.get(tag); + } + return column; + } + + /** + * Get type mapping (lazy) + * + * @param tag + * The feature's MetaID in CDO + * @return the corresponding type mapping + */ + + protected ITypeMapping getTypeMapping(Long tag) + { + + ITypeMapping typeMapping = typeMappings.get(tag); + if (typeMapping == null) + { + addFeature(tag); + typeMapping = typeMappings.get(tag); + } + return typeMapping; + } + + /** + * @param metaID + * @return the column name where the values are stored + */ + + private EStructuralFeature getFeatureByTag(Long tag) + { + return (EStructuralFeature)mappingStrategy.getStore().getMetaDataManager().getMetaInstance(tag); + } + + /** + * @param feature + * The EStructuralFeature + * @return The feature's MetaID in CDO + */ + + protected Long getTagByFeature(EStructuralFeature feature) + { + return mappingStrategy.getStore().getMetaDataManager().getMetaID(feature); + } + + /** + * @param metaID + * The feature's MetaID in CDO + * @return the column name where the values are stored + */ + + /** + * Used by subclasses to indicate which fields should be in the table. I.e. just a pair of name and DBType ... + * + * @author Stefan Winkler + */ + protected static class FieldInfo + { + private String name; + + private DBType dbType; + + public FieldInfo(String name, DBType dbType) + { + this.name = name; + this.dbType = dbType; + } + + public String getName() + { + return name; + } + + public DBType getDbType() + { + return dbType; + } + } +} diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java index 39b3f55e5a..f1b849b810 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java @@ -40,6 +40,7 @@ import org.eclipse.net4j.util.om.trace.ContextTracer; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.util.FeatureMapUtil; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -132,7 +133,14 @@ public abstract class AbstractHorizontalClassMapping implements IClassMapping { if (feature.isMany()) { - listMappings.add(mappingStrategy.createListMapping(eClass, feature)); + if (FeatureMapUtil.isFeatureMap(feature)) + { + listMappings.add(mappingStrategy.createFeatureMapMapping(eClass, feature)); + } + else + { + listMappings.add(mappingStrategy.createListMapping(eClass, feature)); + } } } diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMapping.java new file mode 100644 index 0000000000..5deaffc375 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMapping.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2004 - 2009 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 https://bugs.eclipse.org/bugs/show_bug.cgi?id=271444 + * Christopher Albert - 254455: [DB] Support FeatureMaps https://bugs.eclipse.org/bugs/show_bug.cgi?id=254455 + */ +package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal; + +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.id.CDOIDUtil; +import org.eclipse.emf.cdo.common.revision.CDORevision; +import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; +import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy; +import org.eclipse.emf.cdo.server.internal.db.CDODBSchema; + +import org.eclipse.net4j.db.DBType; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EStructuralFeature; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * This is a featuremap-table mapping for audit mode. It has ID and version columns and no delta support. + * + * @author Eike Stepper + * @since 3.0 + */ +public class AuditFeatureMapTableMapping extends AbstractFeatureMapTableMapping +{ + private static final FieldInfo[] KEY_FIELDS = { new FieldInfo(CDODBSchema.FEATUREMAP_REVISION_ID, DBType.BIGINT), + new FieldInfo(CDODBSchema.FEATUREMAP_VERSION, DBType.INTEGER) }; + + public AuditFeatureMapTableMapping(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature) + { + super(mappingStrategy, eClass, feature); + } + + @Override + protected FieldInfo[] getKeyFields() + { + return KEY_FIELDS; + } + + @Override + protected void setKeyFields(PreparedStatement stmt, CDORevision revision) throws SQLException + { + stmt.setLong(1, CDOIDUtil.getLong(revision.getID())); + stmt.setInt(2, revision.getVersion()); + } + + public void objectRevised(IDBStoreAccessor accessor, CDOID id, long revised) + { + // the audit list mapping does not care about revised references -> NOP + } +} diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditMappingStrategy.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditMappingStrategy.java index d062dd0c82..77c90a2999 100644 --- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditMappingStrategy.java +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditMappingStrategy.java @@ -36,6 +36,12 @@ public class HorizontalAuditMappingStrategy extends AbstractHorizontalMappingStr } @Override + public IListMapping doCreateFeatureMapMapping(EClass containingClass, EStructuralFeature feature) + { + return new AuditFeatureMapTableMapping(this, containingClass, feature); + } + + @Override public boolean hasAuditSupport() { return true; 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 4dbd5bf4b7..93b465671d 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 @@ -36,6 +36,12 @@ public class HorizontalNonAuditMappingStrategy extends AbstractHorizontalMapping } @Override + public IListMapping doCreateFeatureMapMapping(EClass containingClass, EStructuralFeature feature) + { + return new NonAuditFeatureMapTableMapping(this, containingClass, feature); + } + + @Override public boolean hasAuditSupport() { return false; diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditFeatureMapTableMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditFeatureMapTableMapping.java new file mode 100644 index 0000000000..e571d64d0a --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditFeatureMapTableMapping.java @@ -0,0 +1,569 @@ +/** + * Copyright (c) 2004 - 2009 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 https://bugs.eclipse.org/bugs/show_bug.cgi?id=271444 + * Christopher Albert - 254455: [DB] Support FeatureMaps https://bugs.eclipse.org/bugs/show_bug.cgi?id=254455 + */ +package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal; + +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.id.CDOIDUtil; +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.CDODBUtil; +import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; +import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability; +import org.eclipse.emf.cdo.server.db.mapping.IListMapping; +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.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 IListMapping, + IListMappingDeltaSupport +{ + private static final FieldInfo[] KEY_FIELDS = { new FieldInfo(CDODBSchema.FEATUREMAP_REVISION_ID, DBType.BIGINT) }; + + 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().getName()); + 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().getName()); + 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().getName()); + 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().getName()); + 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().getName()); + 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() + { + return KEY_FIELDS; + } + + @Override + protected void setKeyFields(PreparedStatement stmt, CDORevision revision) throws SQLException + { + stmt.setLong(1, CDOIDUtil.getLong(revision.getID())); + } + + public void objectRevised(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); + stmt.setLong(1, CDOIDUtil.getLong(id)); + CDODBUtil.sqlUpdate(stmt, false); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().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) + { + move1up(accessor, id, index, UNBOUNDED_MOVE); + insertValue(accessor, id, index, value); + } + + private void insertValue(IDBStoreAccessor accessor, CDOID id, int index, Object value) + { + PreparedStatement stmt = null; + + try + { + FeatureMap.Entry entry = (FeatureMap.Entry)value; + EStructuralFeature entryFeature = entry.getEStructuralFeature(); + Long tag = getTagByFeature(entryFeature); + String column = getColumnName(tag); + + String sql = sqlInsert; + + stmt = accessor.getStatementCache().getPreparedStatement(sql, ReuseProbability.HIGH); + + stmt.setLong(1, CDOIDUtil.getLong(id)); + int stmtIndex = getKeyFields().length + 1; + + for (int i = 0; i < getColumnNames().size(); i++) + { + if (getColumnNames().get(i).equals(column)) + { + getTypeMapping(tag).setValue(stmt, stmtIndex++, entry.getValue()); + } + else + { + stmt.setNull(stmtIndex++, getDBTypes().get(i).getCode()); + } + } + + stmt.setInt(stmtIndex++, index); + stmt.setLong(stmtIndex++, tag); + + CDODBUtil.sqlUpdate(stmt, true); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().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) + { + PreparedStatement stmt = null; + + try + { + stmt = accessor.getStatementCache().getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH); + stmt.setInt(1, newIndex); + stmt.setLong(2, CDOIDUtil.getLong(id)); + stmt.setInt(3, oldIndex); + CDODBUtil.sqlUpdate(stmt, true); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().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) + { + PreparedStatement stmt = null; + + try + { + stmt = accessor.getStatementCache().getPreparedStatement( + upperIndex == UNBOUNDED_MOVE ? sqlMoveDown : sqlMoveDownWithLimit, ReuseProbability.HIGH); + + stmt.setLong(1, CDOIDUtil.getLong(id)); + stmt.setInt(2, index); + if (upperIndex != UNBOUNDED_MOVE) + { + stmt.setInt(3, upperIndex); + } + + CDODBUtil.sqlUpdate(stmt, false); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().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) + { + PreparedStatement stmt = null; + + try + { + stmt = accessor.getStatementCache().getPreparedStatement( + upperIndex == UNBOUNDED_MOVE ? sqlMoveUp : sqlMoveUpWithLimit, ReuseProbability.HIGH); + stmt.setLong(1, CDOIDUtil.getLong(id)); + stmt.setInt(2, index); + if (upperIndex != UNBOUNDED_MOVE) + { + stmt.setInt(3, upperIndex); + } + + CDODBUtil.sqlUpdate(stmt, false); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().releasePreparedStatement(stmt); + } + } + + private void deleteItem(IDBStoreAccessor accessor, CDOID id, int index) + { + PreparedStatement stmt = null; + + try + { + stmt = accessor.getStatementCache().getPreparedStatement(sqlDeleteItem, ReuseProbability.HIGH); + stmt.setLong(1, CDOIDUtil.getLong(id)); + stmt.setInt(2, index); + CDODBUtil.sqlUpdate(stmt, true); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().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) + { + PreparedStatement stmt = null; + + FeatureMap.Entry entry = (FeatureMap.Entry)value; + EStructuralFeature entryFeature = entry.getEStructuralFeature(); + Long tag = getTagByFeature(entryFeature); + String column = getColumnName(tag); + ITypeMapping mapping = getTypeMapping(tag); + + try + { + stmt = accessor.getStatementCache().getPreparedStatement(sqlUpdateValue, ReuseProbability.HIGH); + stmt.setLong(1, tag); + int colIndex = 2; + + for (int i = 0; i < getColumnNames().size(); i++) + { + String columnName = getColumnNames().get(i); + + if (columnName.equals(column)) + { + mapping.setValue(stmt, colIndex++, entry.getValue()); + } + else + { + stmt.setNull(colIndex++, getDBTypes().get(i).getCode()); + } + } + + stmt.setLong(colIndex++, CDOIDUtil.getLong(id)); + stmt.setInt(colIndex++, index); + CDODBUtil.sqlUpdate(stmt, true); + } + catch (SQLException e) + { + throw new DBException(e); + } + finally + { + accessor.getStatementCache().releasePreparedStatement(stmt); + } + } + + public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int oldVersion, final int newVersion, + 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()); + } + + public void visit(CDORemoveFeatureDelta delta) + { + removeListItem(accessor, id, delta.getIndex()); + } + + public void visit(CDOSetFeatureDelta delta) + { + setListItem(accessor, id, delta.getIndex(), delta.getValue()); + } + + public void visit(CDOUnsetFeatureDelta delta) + { + throw new ImplementationError("Should not be called"); //$NON-NLS-1$ + } + + 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); + } + } +} diff --git a/plugins/org.eclipse.emf.cdo.tests.db/CDO AllTests (H2 non-audit).launch b/plugins/org.eclipse.emf.cdo.tests.db/CDO AllTests (H2 non-audit).launch new file mode 100644 index 0000000000..4a8a8cb614 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.tests.db/CDO AllTests (H2 non-audit).launch @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<launchConfiguration type="org.eclipse.jdt.junit.launchconfig"> +<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS"> +<listEntry value="/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2NonAudit.java"/> +</listAttribute> +<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES"> +<listEntry value="1"/> +</listAttribute> +<listAttribute key="org.eclipse.debug.ui.favoriteGroups"> +<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/> +<listEntry value="org.eclipse.debug.ui.launchGroup.run"/> +</listAttribute> +<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value=""/> +<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/> +<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/> +<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit3"/> +<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.emf.cdo.tests.db.AllTestsDBH2NonAudit"/> +<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.emf.cdo.tests.db"/> +<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms40m -Xmx512m"/> +</launchConfiguration> diff --git a/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2NonAudit.java b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2NonAudit.java new file mode 100644 index 0000000000..9654371519 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2NonAudit.java @@ -0,0 +1,222 @@ +/** + * Copyright (c) 2004 - 2009 Eike Stepper (Berlin, Germany) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Eike Stepper - initial API and implementation + */ +package org.eclipse.emf.cdo.tests.db; + +import org.eclipse.emf.cdo.server.IRepository; +import org.eclipse.emf.cdo.server.db.CDODBUtil; +import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy; +import org.eclipse.emf.cdo.tests.AuditTest; +import org.eclipse.emf.cdo.tests.bugzilla.Bugzilla_252214_Test; +import org.eclipse.emf.cdo.tests.config.impl.ConfigTest; + +import org.eclipse.net4j.db.DBUtil; +import org.eclipse.net4j.db.IDBAdapter; +import org.eclipse.net4j.db.h2.H2Adapter; +import org.eclipse.net4j.util.WrappedException; +import org.eclipse.net4j.util.io.IOUtil; +import org.eclipse.net4j.util.io.TMPUtil; + +import org.h2.jdbcx.JdbcDataSource; + +import javax.sql.DataSource; + +import java.io.File; +import java.sql.Connection; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * @author Eike Stepper + */ +public class AllTestsDBH2NonAudit extends DBConfigs +{ + public static Test suite() + { + return new AllTestsDBH2NonAudit() + .getTestSuite("CDO Tests (DBStoreRepositoryConfig H2 Horizontal - non-audit mode)"); + } + + @Override + protected void initConfigSuites(TestSuite parent) + { + addScenario(parent, COMBINED, AllTestsDBH2NonAudit.H2NonAudit.ReusableFolder.INSTANCE, JVM, NATIVE); + } + + @Override + protected void initTestClasses(List<Class<? extends ConfigTest>> testClasses) + { + super.initTestClasses(testClasses); + + // non-audit mode - remove audit tests + testClasses.remove(AuditTest.class); + testClasses.remove(AuditTest.LocalAuditTest.class); + testClasses.remove(Bugzilla_252214_Test.class); + } + + /** + * @author Eike Stepper + */ + public static class H2NonAudit extends DBStoreRepositoryConfig + { + private static final long serialVersionUID = 1L; + + public static final AllTestsDBH2NonAudit.H2NonAudit INSTANCE = new H2NonAudit("DBStore: H2 (non-audit)"); + + protected transient File dbFolder; + + public H2NonAudit(String name) + { + super(name); + } + + @Override + protected void initRepositoryProperties(Map<String, String> props) + { + super.initRepositoryProperties(props); + props.put(IRepository.Props.SUPPORTING_AUDITS, "false"); + } + + @Override + protected IMappingStrategy createMappingStrategy() + { + return CDODBUtil.createHorizontalMappingStrategy(false); + } + + @Override + protected IDBAdapter createDBAdapter() + { + return new H2Adapter(); + } + + @Override + protected DataSource createDataSource(String repoName) + { + if (dbFolder == null) + { + dbFolder = createDBFolder(); + tearDownClean(); + } + + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL("jdbc:h2:" + dbFolder.getAbsolutePath() + "/h2test;SCHEMA=" + repoName); + return dataSource; + } + + protected void tearDownClean() + { + IOUtil.delete(dbFolder); + } + + protected File createDBFolder() + { + return TMPUtil.createTempFolder("h2_", "_test", new File("/temp")); + } + + /** + * @author Eike Stepper + */ + public static class ReusableFolder extends H2NonAudit + { + private static final long serialVersionUID = 1L; + + public static final ReusableFolder INSTANCE = new ReusableFolder("DBStore: H2 (Reusable Folder)"); + + private static File reusableFolder; + + private static JdbcDataSource defaultDataSource; + + private transient ArrayList<String> repoNames = new ArrayList<String>(); + + public ReusableFolder(String name) + { + super(name); + } + + @Override + protected DataSource createDataSource(String repoName) + { + if (reusableFolder == null) + { + reusableFolder = createDBFolder(); + IOUtil.delete(reusableFolder); + } + dbFolder = reusableFolder; + + if (defaultDataSource == null) + { + defaultDataSource = new JdbcDataSource(); + defaultDataSource.setURL("jdbc:h2:" + dbFolder.getAbsolutePath() + "/h2test"); + } + + Connection conn = null; + Statement stmt = null; + try + { + conn = defaultDataSource.getConnection(); + stmt = conn.createStatement(); + stmt.execute("DROP SCHEMA IF EXISTS " + repoName); + stmt.execute("CREATE SCHEMA " + repoName); + } + catch (Exception e) + { + e.printStackTrace(); + } + finally + { + DBUtil.close(conn); + DBUtil.close(stmt); + } + + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL("jdbc:h2:" + dbFolder.getAbsolutePath() + "/h2test;SCHEMA=" + repoName); + + return dataSource; + } + + @Override + protected void tearDownClean() + { + for (String repoName : repoNames) + { + tearDownClean(repoName); + } + } + + protected void tearDownClean(String repoName) + { + reusableFolder.deleteOnExit(); + Connection connection = null; + Statement stmt = null; + + try + { + connection = defaultDataSource.getConnection(); + stmt = connection.createStatement(); + stmt.execute("DROP SCHEMA " + repoName); + } + catch (Exception ex) + { + throw WrappedException.wrap(ex); + } + finally + { + DBUtil.close(stmt); + DBUtil.close(connection); + } + } + } + } +} diff --git a/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/DBConfigs.java b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/DBConfigs.java index 80a349550a..33a877fce0 100644 --- a/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/DBConfigs.java +++ b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/DBConfigs.java @@ -11,7 +11,6 @@ package org.eclipse.emf.cdo.tests.db; import org.eclipse.emf.cdo.tests.AllTestsAllConfigs; -import org.eclipse.emf.cdo.tests.FeatureMapTest; import org.eclipse.emf.cdo.tests.XATransactionTest; import org.eclipse.emf.cdo.tests.bugzilla.Bugzilla_258933_Test; import org.eclipse.emf.cdo.tests.config.impl.ConfigTest; @@ -38,8 +37,5 @@ public abstract class DBConfigs extends AllTestsAllConfigs // fails because of Bug 284110 testClasses.remove(Bugzilla_258933_Test.class); testClasses.add(DISABLE_Bugzilla_258933_Test.class); - - // fails because of Bug 254455 - testClasses.remove(FeatureMapTest.class); } } |