summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Winkler2010-10-25 09:37:59 (EDT)
committerStefan Winkler2010-10-25 09:37:59 (EDT)
commit8ac0e8d84a25256c5c7c30e3cee48e5bd7b0fb89 (patch)
treedfd7610bf62e57c02930809f8dc5b8a5d4cd14e8
parent1b3f0a649f05e18506a1e8f4a5e72b922d87c3e1 (diff)
downloadcdo-8ac0e8d84a25256c5c7c30e3cee48e5bd7b0fb89.zip
cdo-8ac0e8d84a25256c5c7c30e3cee48e5bd7b0fb89.tar.gz
cdo-8ac0e8d84a25256c5c7c30e3cee48e5bd7b0fb89.tar.bz2
Rangebased Mapping - Branching Step 1
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/IListMappingDeltaSupport.java5
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMappingWithRanges.java4
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditListTableMappingWithRanges.java4
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BranchingFeatureMapTableMappingWithRanges.java1309
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BranchingListTableMappingWithRanges.java1187
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditClassMapping.java5
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalBranchingClassMapping.java194
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalBranchingMappingStrategyWithRanges.java77
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditClassMapping.java5
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditFeatureMapTableMapping.java4
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java4
-rw-r--r--plugins/org.eclipse.emf.cdo.tests.db/CDO AllTests (H2 branching).launch2
-rw-r--r--plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2Branching.java34
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java1
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTest.java166
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTestWithCacheClear.java15
16 files changed, 2993 insertions, 23 deletions
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/IListMappingDeltaSupport.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/IListMappingDeltaSupport.java
index 60f9e8e..6636e74 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/IListMappingDeltaSupport.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/mapping/IListMappingDeltaSupport.java
@@ -39,7 +39,8 @@ public interface IListMappingDeltaSupport
* the creation date for the new revision
* @param delta
* the {@link CDOListFeatureDelta} which contains the list deltas.
+ * @since 4.0
*/
- public void processDelta(IDBStoreAccessor accessor, CDOID id, int oldVersion, int newVersion, long created,
- CDOListFeatureDelta delta);
+ public void processDelta(IDBStoreAccessor accessor, CDOID id, int branchId, int oldVersion, int newVersion,
+ long created, CDOListFeatureDelta delta);
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMappingWithRanges.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMappingWithRanges.java
index 5fd72fe..f2ffa29 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMappingWithRanges.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditFeatureMapTableMappingWithRanges.java
@@ -783,8 +783,8 @@ public class AuditFeatureMapTableMappingWithRanges extends BasicAbstractListTabl
clearList(accessor, id, revision.getVersion(), FINAL_VERSION);
}
- public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int oldVersion, final int newVersion,
- long created, CDOListFeatureDelta delta)
+ public void processDelta(final IDBStoreAccessor accessor, final CDOID id, final int branchId, int oldVersion,
+ final int newVersion, long created, CDOListFeatureDelta delta)
{
IRepository repo = accessor.getStore().getRepository();
InternalCDORevision originalRevision = (InternalCDORevision)repo.getRevisionManager().getRevision(id,
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditListTableMappingWithRanges.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditListTableMappingWithRanges.java
index 6587358..9b774df 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditListTableMappingWithRanges.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AuditListTableMappingWithRanges.java
@@ -606,8 +606,8 @@ public class AuditListTableMappingWithRanges extends BasicAbstractListTableMappi
clearList(accessor, id, revision.getVersion(), FINAL_VERSION);
}
- public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int oldVersion, final int newVersion,
- long created, CDOListFeatureDelta delta)
+ public void processDelta(final IDBStoreAccessor accessor, final CDOID id, final int branchId, int oldVersion,
+ final int newVersion, long created, CDOListFeatureDelta delta)
{
IRepository repo = accessor.getStore().getRepository();
InternalCDORevision originalRevision = (InternalCDORevision)repo.getRevisionManager().getRevision(id,
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BranchingFeatureMapTableMappingWithRanges.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BranchingFeatureMapTableMappingWithRanges.java
new file mode 100644
index 0000000..c166786
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BranchingFeatureMapTableMappingWithRanges.java
@@ -0,0 +1,1309 @@
+/**
+ * Copyright (c) 2004 - 2010 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 - Bug 271444: [DB] Multiple refactorings bug 271444
+ * Christopher Albert - Bug 254455: [DB] Support FeatureMaps bug 254455
+ * Victor Roldan Betancort - Bug 283998: [DB] Chunk reading for multiple chunks fails
+ * Lothar Werzinger - Bug 296440: [DB] Change RDB schema to improve scalability of to-many references in audit mode
+ * Stefan Winkler - cleanup, merge and maintenance
+ * Stefan Winkler - Bug 285426: [DB] Implement user-defined typeMapping support
+ */
+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.CDOList;
+import org.eclipse.emf.cdo.common.revision.CDORevision;
+import org.eclipse.emf.cdo.common.revision.CDORevisionUtil;
+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.IStoreAccessor.QueryXRefsContext;
+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;
+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.emf.cdo.server.internal.db.bundle.OM;
+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.IDBIndex.Type;
+import org.eclipse.net4j.db.ddl.IDBTable;
+import org.eclipse.net4j.util.ImplementationError;
+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 is a featuremap-table mapping for audit mode. It is optimized for frequent insert operations at the list's end,
+ * which causes just 1 DB row to be changed. This is achieved by introducing a version range (columns
+ * {@link CDODBSchema#LIST_REVISION_VERSION_ADDED cdo_version_added} and
+ * {@link CDODBSchema#LIST_REVISION_VERSION_REMOVED cdo_version_removed}) which records for which revisions a particular
+ * entry existed. Also, this mapping is mainly optimized for potentially very large lists: the need for having the
+ * complete list stored in memory to do in-the-middle-moved and inserts is traded in for a few more DB access
+ * operations.
+ *
+ * @author Eike Stepper
+ * @author Stefan Winkler
+ * @author Lothar Werzinger
+ * @since 3.0
+ */
+public class BranchingFeatureMapTableMappingWithRanges extends BasicAbstractListTableMapping implements
+ IListMappingDeltaSupport
+{
+ private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG,
+ BranchingFeatureMapTableMappingWithRanges.class);
+
+ /**
+ * Used to clean up lists for detached objects.
+ */
+ private static final int FINAL_VERSION = Integer.MAX_VALUE;
+
+ /**
+ * 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;
+
+ // --------- SQL strings - see initSQLStrings() -----------------
+ private String sqlSelectChunksPrefix;
+
+ private String sqlOrderByIndex;
+
+ protected String sqlInsert;
+
+ private String sqlGetListLastIndex;
+
+ private List<DBType> dbTypes;
+
+ private String sqlRemoveEntry;
+
+ private String sqlDeleteEntry;
+
+ private String sqlUpdateIndex;
+
+ private String sqlGetValue;
+
+ private String sqlClearList;
+
+ private String sqlDeleteList;
+
+ public BranchingFeatureMapTableMappingWithRanges(IMappingStrategy mappingStrategy, EClass eClass,
+ EStructuralFeature feature)
+ {
+ super(mappingStrategy, eClass, feature);
+ initDBTypes();
+ initTable();
+ initSQLStrings();
+ }
+
+ private void initDBTypes()
+ {
+ // TODO add annotation processing here ...
+ ITypeMapping.Registry registry = ITypeMapping.Registry.INSTANCE;
+ dbTypes = new ArrayList<DBType>(registry.getDefaultFeatureMapDBTypes());
+ }
+
+ private void initTable()
+ {
+ String tableName = getMappingStrategy().getTableName(getContainingClass(), getFeature());
+ table = getMappingStrategy().getStore().getDBSchema().addTable(tableName);
+
+ // add fields for CDOID
+ IDBField idField = table.addField(CDODBSchema.FEATUREMAP_REVISION_ID, DBType.INTEGER);
+
+ IDBField branchField = table.addField(CDODBSchema.LIST_REVISION_BRANCH, DBType.INTEGER);
+
+ // add fields for version range
+ IDBField versionAddedField = table.addField(CDODBSchema.FEATUREMAP_VERSION_ADDED, DBType.INTEGER);
+ IDBField versionRemovedField = table.addField(CDODBSchema.FEATUREMAP_VERSION_REMOVED, DBType.INTEGER);
+
+ // 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, idField);
+ table.addIndex(Type.NON_UNIQUE, branchField);
+ table.addIndex(Type.NON_UNIQUE, versionAddedField);
+ table.addIndex(Type.NON_UNIQUE, versionRemovedField);
+ table.addIndex(Type.NON_UNIQUE, idxField);
+ table.addIndex(Type.NON_UNIQUE, tagField);
+ }
+
+ public Collection<IDBTable> getDBTables()
+ {
+ return Arrays.asList(table);
+ }
+
+ private void initSQLStrings()
+ {
+ String tableName = getTable().getName();
+
+ // ---------------- SELECT to read chunks ----------------------------
+ StringBuilder builder = new StringBuilder();
+ builder.append("SELECT "); //$NON-NLS-1$
+
+ builder.append(CDODBSchema.FEATUREMAP_TAG);
+ builder.append(", "); //$NON-NLS-1$
+
+ Iterator<String> iter = columnNames.iterator();
+ while (iter.hasNext())
+ {
+ builder.append(iter.next());
+ if (iter.hasNext())
+ {
+ builder.append(", "); //$NON-NLS-1$
+ }
+ }
+
+ builder.append(" FROM "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED);
+ builder.append("<=? AND ("); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ builder.append(" IS NULL OR "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ builder.append(">?)"); //$NON-NLS-1$
+ sqlSelectChunksPrefix = builder.toString();
+
+ sqlOrderByIndex = " ORDER BY " + CDODBSchema.FEATUREMAP_IDX; //$NON-NLS-1$
+
+ // ----------------- count list size --------------------------
+
+ builder = new StringBuilder("SELECT count(1) FROM "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED);
+ builder.append("<=? AND ("); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ builder.append(" IS NULL OR "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ builder.append(">?)"); //$NON-NLS-1$
+ sqlGetListLastIndex = builder.toString();
+
+ // ----------------- INSERT - prefix -----------------
+ builder = new StringBuilder("INSERT INTO "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append("("); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_BRANCH);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_IDX);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VALUE);
+
+ 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 < columnNames.size(); i++)
+ {
+ builder.append("?, "); //$NON-NLS-1$
+ }
+
+ builder.append("?, ?)"); //$NON-NLS-1$
+ sqlInsert = builder.toString();
+
+ // ----------------- remove current entry -----------------
+ builder = new StringBuilder("UPDATE "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" SET "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ 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_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_IDX);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ builder.append(" IS NULL"); //$NON-NLS-1$
+ sqlRemoveEntry = builder.toString();
+
+ // ----------------- delete temporary entry -----------------
+ builder = new StringBuilder("DELETE FROM "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_IDX);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED);
+ builder.append("=?"); //$NON-NLS-1$
+ sqlDeleteEntry = builder.toString();
+
+ // ----------------- update index -----------------
+ builder = new StringBuilder("UPDATE "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" SET "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_IDX);
+ builder.append("=? WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_IDX);
+ builder.append("=?"); //$NON-NLS-1$
+ sqlUpdateIndex = builder.toString();
+
+ // ----------------- get current value -----------------
+ builder = new StringBuilder("SELECT "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_TAG);
+ builder.append(", "); //$NON-NLS-1$
+
+ iter = columnNames.iterator();
+ while (iter.hasNext())
+ {
+ builder.append(iter.next());
+ if (iter.hasNext())
+ {
+ builder.append(", "); //$NON-NLS-1$
+ }
+ }
+
+ builder.append(" FROM "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_IDX);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ builder.append(" IS NULL"); //$NON-NLS-1$
+ sqlGetValue = builder.toString();
+
+ // ----------- clear list items -------------------------
+ builder = new StringBuilder("UPDATE "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" SET "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ 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_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ builder.append(" IS NULL"); //$NON-NLS-1$
+ sqlClearList = builder.toString();
+
+ // ----------- delete temporary list items -------------------------
+ builder = new StringBuilder("DELETE FROM "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_ADDED);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.FEATUREMAP_VERSION_REMOVED);
+ builder.append(" IS NULL"); //$NON-NLS-1$
+ sqlDeleteList = builder.toString();
+ }
+
+ protected List<DBType> getDBTypes()
+ {
+ return dbTypes;
+ }
+
+ 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;
+ }
+
+ // 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}", getContainingClass().getName(), getFeature() //$NON-NLS-1$
+ .getName(), revision.getID(), revision.getVersion());
+ }
+
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+ ResultSet resultSet = null;
+
+ try
+ {
+ String sql = sqlSelectChunksPrefix + sqlOrderByIndex;
+
+ pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.HIGH);
+
+ pstmt.setLong(1, CDOIDUtil.getLong(revision.getID()));
+ pstmt.setInt(2, revision.getBranch().getID());
+ pstmt.setInt(3, revision.getVersion());
+ pstmt.setInt(4, revision.getVersion());
+
+ 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);
+ Object value = getTypeMapping(tag).readValue(resultSet);
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Read value for index {0} from result set: {1}", list.size(), value); //$NON-NLS-1$
+ }
+
+ list.add(CDORevisionUtil.createFeatureMapEntry(getFeatureByTag(tag), value));
+ }
+
+ while (listSize-- >= 0)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Adding UNINITIALIZED for index {0} ", list.size()); //$NON-NLS-1$
+ }
+
+ list.add(InternalCDOList.UNINITIALIZED);
+ }
+ }
+ catch (SQLException ex)
+ {
+ throw new DBException(ex);
+ }
+ finally
+ {
+ DBUtil.close(resultSet);
+ statementCache.releasePreparedStatement(pstmt);
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Reading list values done for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), //$NON-NLS-1$
+ getFeature().getName(), revision.getID(), revision.getVersion());
+ }
+ }
+
+ private void addFeature(Long tag)
+ {
+ EStructuralFeature modelFeature = getFeatureByTag(tag);
+
+ ITypeMapping typeMapping = getMappingStrategy().createValueMapping(modelFeature);
+ String column = CDODBSchema.FEATUREMAP_VALUE + "_" + typeMapping.getDBType(); //$NON-NLS-1$
+
+ 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)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+ ResultSet resultSet = null;
+
+ try
+ {
+ pstmt = statementCache.getPreparedStatement(sqlGetListLastIndex, ReuseProbability.HIGH);
+
+ pstmt.setLong(1, CDOIDUtil.getLong(revision.getID()));
+ pstmt.setInt(2, revision.getBranch().getID());
+ pstmt.setInt(3, revision.getVersion());
+ pstmt.setInt(4, revision.getVersion());
+
+ resultSet = pstmt.executeQuery();
+ if (!resultSet.next())
+ {
+ throw new DBException("Count expects exactly one result");
+ }
+
+ int result = resultSet.getInt(1) - 1;
+ if (TRACER.isEnabled())
+ {
+ TRACER.trace("Read list last index = " + result); //$NON-NLS-1$
+ }
+
+ return result;
+ }
+ catch (SQLException ex)
+ {
+ throw new DBException(ex);
+ }
+ finally
+ {
+ DBUtil.close(resultSet);
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+
+ public final void readChunks(IDBStoreChunkReader chunkReader, List<Chunk> chunks, String where)
+ {
+ CDORevision revision = chunkReader.getRevision();
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Reading list chunk values for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), //$NON-NLS-1$
+ getFeature().getName(), revision.getID(), revision.getVersion());
+ }
+
+ IPreparedStatementCache statementCache = chunkReader.getAccessor().getStatementCache();
+ PreparedStatement pstmt = null;
+ ResultSet resultSet = null;
+
+ try
+ {
+ StringBuilder builder = new StringBuilder(sqlSelectChunksPrefix);
+ if (where != null)
+ {
+ builder.append(" AND "); //$NON-NLS-1$
+ builder.append(where);
+ }
+
+ builder.append(sqlOrderByIndex);
+
+ String sql = builder.toString();
+ pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.LOW);
+ pstmt.setLong(1, CDOIDUtil.getLong(revision.getID()));
+ pstmt.setInt(2, revision.getBranch().getID());
+ pstmt.setInt(3, revision.getVersion());
+ pstmt.setInt(4, revision.getVersion());
+
+ resultSet = pstmt.executeQuery();
+
+ Chunk chunk = null;
+ int chunkSize = 0;
+ int chunkIndex = 0;
+ int indexInChunk = 0;
+
+ while (resultSet.next())
+ {
+ Long tag = resultSet.getLong(1);
+ Object value = getTypeMapping(tag).readValue(resultSet);
+
+ 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(), //$NON-NLS-1$
+ chunkSize);
+ }
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Read value for chunk index {0} from result set: {1}", indexInChunk, value); //$NON-NLS-1$
+ }
+
+ chunk.add(indexInChunk++, CDORevisionUtil.createFeatureMapEntry(getFeatureByTag(tag), value));
+ if (indexInChunk == chunkSize)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Chunk finished"); //$NON-NLS-1$
+ }
+
+ chunk = null;
+ indexInChunk = 0;
+ }
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Reading list chunk values done for feature {0}.{1} of {2}v{3}", getContainingClass().getName(), //$NON-NLS-1$
+ getTagByFeature(getFeature()), revision.getID(), revision.getVersion());
+ }
+ }
+ catch (SQLException ex)
+ {
+ throw new DBException(ex);
+ }
+ finally
+ {
+ DBUtil.close(resultSet);
+ statementCache.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);
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Writing done"); //$NON-NLS-1$
+ }
+ }
+
+ protected final void writeValue(IDBStoreAccessor accessor, CDORevision revision, int idx, Object value)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER
+ .format(
+ "Writing value for feature {0}.{1} index {2} of {3}v{4} : {5}", getContainingClass().getName(), getTagByFeature(getFeature()), idx, revision.getID(), revision.getVersion(), value); //$NON-NLS-1$
+ }
+
+ addEntry(accessor, revision.getID(), revision.getBranch().getID(), revision.getVersion(), idx, value);
+ }
+
+ /**
+ * 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)getMappingStrategy().getStore().getMetaDataManager().getMetaInstance(tag);
+ }
+
+ /**
+ * @param feature
+ * The EStructuralFeature
+ * @return The feature's MetaID in CDO
+ */
+ protected Long getTagByFeature(EStructuralFeature feature)
+ {
+ return getMappingStrategy().getStore().getMetaDataManager().getMetaID(feature);
+ }
+
+ /**
+ * 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, int branchId, int oldVersion, int newVersion)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmtDeleteTemp = null;
+ PreparedStatement pstmtClear = null;
+
+ try
+ {
+ // delete temporary entries
+ pstmtDeleteTemp = statementCache.getPreparedStatement(sqlDeleteList, ReuseProbability.HIGH);
+ pstmtDeleteTemp.setLong(1, CDOIDUtil.getLong(id));
+ pstmtDeleteTemp.setInt(2, branchId);
+ pstmtDeleteTemp.setInt(3, newVersion);
+
+ int result = CDODBUtil.sqlUpdate(pstmtDeleteTemp, false);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("DeleteList result: {0}", result); //$NON-NLS-1$
+ }
+
+ // clear rest of the list
+ pstmtClear = statementCache.getPreparedStatement(sqlClearList, ReuseProbability.HIGH);
+ pstmtClear.setInt(1, newVersion);
+ pstmtClear.setLong(2, CDOIDUtil.getLong(id));
+ pstmtClear.setInt(3, branchId);
+
+ result = CDODBUtil.sqlUpdate(pstmtClear, false);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("ClearList result: {0}", result); //$NON-NLS-1$
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmtDeleteTemp);
+ statementCache.releasePreparedStatement(pstmtClear);
+ }
+ }
+
+ public void objectDetached(IDBStoreAccessor accessor, CDOID id, long revised)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("objectRevised {0}: {1}", id, revised); //$NON-NLS-1$
+ }
+
+ CDORevision revision = accessor.getTransaction().getRevision(id);
+ int branchId = accessor.getTransaction().getBranch().getID();
+ clearList(accessor, id, branchId, revision.getVersion(), FINAL_VERSION);
+ }
+
+ public void processDelta(final IDBStoreAccessor accessor, final CDOID id, final int branchId, int oldVersion,
+ final int newVersion, long created, CDOListFeatureDelta delta)
+ {
+ InternalCDORevision originalRevision = (InternalCDORevision)accessor.getTransaction().getRevision(id);
+ int oldListSize = originalRevision.getList(getFeature()).size();
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("ListTableMapping.processDelta for revision {0} - previous list size: {1}", originalRevision, //$NON-NLS-1$
+ oldListSize);
+ }
+
+ // let the visitor collect the changes
+ ListDeltaVisitor visitor = new ListDeltaVisitor(accessor, originalRevision, branchId, oldVersion, newVersion);
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Processing deltas..."); //$NON-NLS-1$
+ }
+
+ for (CDOFeatureDelta listDelta : delta.getListChanges())
+ {
+ listDelta.accept(visitor);
+ }
+ }
+
+ private class ListDeltaVisitor implements CDOFeatureDeltaVisitor
+ {
+ private IDBStoreAccessor accessor;
+
+ private InternalCDORevision originalRevision;
+
+ private CDOID id;
+
+ private int branchID;
+
+ private int oldVersion;
+
+ private int newVersion;
+
+ private int lastIndex;
+
+ public ListDeltaVisitor(IDBStoreAccessor accessor, InternalCDORevision originalRevision, int targetBranchID,
+ int oldVersion, int newVersion)
+ {
+ this.accessor = accessor;
+ this.originalRevision = originalRevision;
+ id = this.originalRevision.getID();
+ branchID = targetBranchID;
+ this.oldVersion = oldVersion;
+ this.newVersion = newVersion;
+ lastIndex = originalRevision.getList(getFeature()).size() - 1;
+ }
+
+ public void visit(CDOMoveFeatureDelta delta)
+ {
+ int fromIdx = delta.getOldPosition();
+ int toIdx = delta.getNewPosition();
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Moving: {0} to {1}", fromIdx, toIdx); //$NON-NLS-1$
+ }
+
+ Object value = getValue(accessor, id, branchID, fromIdx);
+
+ // remove the item
+ removeEntry(accessor, id, branchID, oldVersion, newVersion, fromIdx);
+
+ // adjust indexes and shift either up or down
+ if (fromIdx < toIdx)
+ {
+ moveOneUp(accessor, id, branchID, oldVersion, newVersion, fromIdx + 1, toIdx);
+ }
+ else
+ { // fromIdx > toIdx here
+ moveOneDown(accessor, id, branchID, oldVersion, newVersion, toIdx, fromIdx - 1);
+ }
+
+ // create the item
+ addEntry(accessor, id, branchID, newVersion, toIdx, value);
+ }
+
+ public void visit(CDOAddFeatureDelta delta)
+ {
+ int startIndex = delta.getIndex();
+ int endIndex = lastIndex;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Adding at: {0}", startIndex); //$NON-NLS-1$
+ }
+
+ if (startIndex <= endIndex)
+ {
+ // make room for the new item
+ moveOneDown(accessor, id, branchID, oldVersion, newVersion, startIndex, endIndex);
+ }
+
+ // create the item
+ addEntry(accessor, id, branchID, newVersion, startIndex, delta.getValue());
+
+ ++lastIndex;
+ }
+
+ public void visit(CDORemoveFeatureDelta delta)
+ {
+ int startIndex = delta.getIndex();
+ int endIndex = lastIndex;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Removing at: {0}", startIndex); //$NON-NLS-1$
+ }
+
+ // remove the item
+ removeEntry(accessor, id, branchID, oldVersion, newVersion, startIndex);
+
+ // make room for the new item
+ moveOneUp(accessor, id, branchID, oldVersion, newVersion, startIndex + 1, endIndex);
+
+ --lastIndex;
+ }
+
+ public void visit(CDOSetFeatureDelta delta)
+ {
+ int index = delta.getIndex();
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Setting at: {0}", index); //$NON-NLS-1$
+ }
+
+ // remove the item
+ removeEntry(accessor, id, branchID, oldVersion, newVersion, index);
+
+ // create the item
+ addEntry(accessor, id, branchID, newVersion, index, delta.getValue());
+ }
+
+ public void visit(CDOUnsetFeatureDelta delta)
+ {
+ if (delta.getFeature().isUnsettable())
+ {
+ throw new ImplementationError("Should not be called"); //$NON-NLS-1$
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Unsetting"); //$NON-NLS-1$
+ }
+
+ clearList(accessor, id, branchID, oldVersion, newVersion);
+ lastIndex = -1;
+ }
+
+ public void visit(CDOListFeatureDelta delta)
+ {
+ throw new ImplementationError("Should not be called"); //$NON-NLS-1$
+ }
+
+ public void visit(CDOClearFeatureDelta delta)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Clearing"); //$NON-NLS-1$
+ }
+
+ clearList(accessor, id, branchID, oldVersion, newVersion);
+ lastIndex = -1;
+ }
+
+ public void visit(CDOContainerFeatureDelta delta)
+ {
+ throw new ImplementationError("Should not be called"); //$NON-NLS-1$
+ }
+
+ private void moveOneUp(IDBStoreAccessor accessor, CDOID id, int branchId, int oldVersion, int newVersion,
+ int startIndex, int endIndex)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+
+ try
+ {
+ pstmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH);
+
+ for (int index = startIndex; index <= endIndex; ++index)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp moving: {0} -> {1}", index, index - 1); //$NON-NLS-1$
+ }
+
+ int stmtIndex = 1;
+ pstmt.setInt(stmtIndex++, index - 1);
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, newVersion);
+ pstmt.setInt(stmtIndex++, index);
+
+ int result = CDODBUtil.sqlUpdate(pstmt, false);
+ switch (result)
+ {
+ case 0:
+ Object value = getValue(accessor, id, branchId, index);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp remove: {0}", index); //$NON-NLS-1$
+ }
+
+ removeEntry(accessor, id, branchId, oldVersion, newVersion, index);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp add: {0}", index - 1); //$NON-NLS-1$
+ }
+
+ addEntry(accessor, id, branchId, newVersion, index - 1, value);
+ break;
+
+ case 1:
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp updated: {0} -> {1}", index, index - 1); //$NON-NLS-1$
+ }
+
+ break;
+
+ default:
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp Too many results: {0} -> {1}: {2}", index, index + 1, result); //$NON-NLS-1$
+ }
+
+ throw new DBException("Too many results"); //$NON-NLS-1$
+ }
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+
+ private void moveOneDown(IDBStoreAccessor accessor, CDOID id, int branchId, int oldVersion, int newVersion,
+ int startIndex, int endIndex)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+
+ try
+ {
+ pstmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH);
+ for (int index = endIndex; index >= startIndex; --index)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown moving: {0} -> {1}", index, index + 1); //$NON-NLS-1$
+ }
+
+ int stmtIndex = 1;
+ pstmt.setInt(stmtIndex++, index + 1);
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, newVersion);
+ pstmt.setInt(stmtIndex++, index);
+
+ int result = CDODBUtil.sqlUpdate(pstmt, false);
+ switch (result)
+ {
+ case 0:
+ Object value = getValue(accessor, id, branchId, index);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown remove: {0}", index); //$NON-NLS-1$
+ }
+
+ removeEntry(accessor, id, branchId, oldVersion, newVersion, index);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown add: {0}", index + 1); //$NON-NLS-1$
+ }
+
+ addEntry(accessor, id, branchId, newVersion, index + 1, value);
+ break;
+
+ case 1:
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown updated: {0} -> {1}", index, index + 1); //$NON-NLS-1$
+ }
+
+ break;
+
+ default:
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown Too many results: {0} -> {1}: {2}", index, index + 1, result); //$NON-NLS-1$
+ }
+
+ throw new DBException("Too many results"); //$NON-NLS-1$
+ }
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+ }
+
+ private void addEntry(IDBStoreAccessor accessor, CDOID id, int branchId, int version, int index, Object value)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Adding value for feature() {0}.{1} index {2} of {3}v{4} : {5}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), index, id, version, value);
+ }
+
+ try
+ {
+ FeatureMap.Entry entry = (FeatureMap.Entry)value;
+ EStructuralFeature entryFeature = entry.getEStructuralFeature();
+ Long tag = getTagByFeature(entryFeature);
+ String column = getColumnName(tag);
+
+ pstmt = statementCache.getPreparedStatement(sqlInsert, ReuseProbability.HIGH);
+
+ int stmtIndex = 1;
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, version);
+ pstmt.setInt(stmtIndex++, index);
+
+ for (int i = 0; i < columnNames.size(); i++)
+ {
+ if (columnNames.get(i).equals(column))
+ {
+ getTypeMapping(tag).setValue(pstmt, stmtIndex++, entry.getValue());
+ }
+ else
+ {
+ pstmt.setNull(stmtIndex++, getDBTypes().get(i).getCode());
+ }
+ }
+
+ pstmt.setInt(stmtIndex++, index);
+ pstmt.setLong(stmtIndex++, tag);
+ CDODBUtil.sqlUpdate(pstmt, true);
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ catch (IllegalStateException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+
+ private void removeEntry(IDBStoreAccessor accessor, CDOID id, int branchId, int oldVersion, int newVersion, int index)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), index, id, newVersion);
+ }
+
+ try
+ {
+ // try to delete a temporary entry first
+ pstmt = statementCache.getPreparedStatement(sqlDeleteEntry, ReuseProbability.HIGH);
+
+ int stmtIndex = 1;
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, index);
+ pstmt.setInt(stmtIndex++, newVersion);
+
+ int result = CDODBUtil.sqlUpdate(pstmt, false);
+ if (result == 1)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("removeEntry deleted: {0}", index); //$NON-NLS-1$
+ }
+ }
+ else if (result > 1)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("removeEntry Too many results: {0}: {1}", index, result); //$NON-NLS-1$
+ }
+
+ throw new DBException("Too many results"); //$NON-NLS-1$
+ }
+ else
+ {
+ // no temporary entry found, so mark the entry as removed
+ statementCache.releasePreparedStatement(pstmt);
+ pstmt = statementCache.getPreparedStatement(sqlRemoveEntry, ReuseProbability.HIGH);
+
+ stmtIndex = 1;
+ pstmt.setInt(stmtIndex++, newVersion);
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, index);
+ CDODBUtil.sqlUpdate(pstmt, true);
+ }
+ }
+ catch (SQLException e)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4} FAILED {5}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), index, id, newVersion, e.getMessage());
+ }
+
+ throw new DBException(e);
+ }
+ catch (IllegalStateException e)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4} FAILED {5}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), index, id, newVersion, e.getMessage());
+ }
+
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+
+ private FeatureMap.Entry getValue(IDBStoreAccessor accessor, CDOID id, int branchId, int index)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+ FeatureMap.Entry result = null;
+
+ try
+ {
+ pstmt = statementCache.getPreparedStatement(sqlGetValue, ReuseProbability.HIGH);
+
+ int stmtIndex = 1;
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, index);
+
+ ResultSet resultSet = pstmt.executeQuery();
+ if (!resultSet.next())
+ {
+ throw new DBException("getValue expects exactly one result");
+ }
+
+ Long tag = resultSet.getLong(1);
+ Object value = getTypeMapping(tag).readValue(resultSet);
+ result = CDORevisionUtil.createFeatureMapEntry(getFeatureByTag(tag), value);
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Read value (index {0}) from result set: {1}", index, result); //$NON-NLS-1$
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+
+ return result;
+ }
+
+ public final boolean queryXRefs(IDBStoreAccessor accessor, String mainTableName, String mainTableWhere,
+ QueryXRefsContext context, String idString)
+ {
+ // must never be called (a feature map is not associated with an EReference feature, so XRefs are nor supported
+ // here)
+ throw new ImplementationError("Should never be called!");
+ }
+}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BranchingListTableMappingWithRanges.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BranchingListTableMappingWithRanges.java
new file mode 100644
index 0000000..7da6dc5
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/BranchingListTableMappingWithRanges.java
@@ -0,0 +1,1187 @@
+/**
+ * Copyright (c) 2004 - 2010 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
+ *
+ * This class has been derived from AbstractListTableMapping
+ *
+ * Contributors:
+ * Eike Stepper - initial API and implementation
+ * Lothar Werzinger - Bug 296440: [DB] Change RDB schema to improve scalability of to-many references in audit mode
+ * Stefan Winkler - cleanup, merge and maintenance
+ */
+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.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.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.IStoreAccessor.QueryXRefsContext;
+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;
+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.emf.cdo.server.internal.db.bundle.OM;
+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.IDBIndex.Type;
+import org.eclipse.net4j.db.ddl.IDBTable;
+import org.eclipse.net4j.util.ImplementationError;
+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.EReference;
+import org.eclipse.emf.ecore.EStructuralFeature;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * This is a list-table mapping for audit mode. It is optimized for frequent insert operations at the list's end, which
+ * causes just 1 DB row to be changed. This is achieved by introducing a version range (columns cdo_version_added and
+ * cdo_version_removed) which records for which revisions a particular entry existed. Also, this mapping is mainly
+ * optimized for potentially very large lists: the need for having the complete list stored in memopy to do
+ * in-the-middle-moved and inserts is traded in for a few more DB access operations.
+ *
+ * @author Eike Stepper
+ * @author Stefan Winkler
+ * @author Lothar Werzinger
+ */
+public class BranchingListTableMappingWithRanges extends BasicAbstractListTableMapping implements
+ IListMappingDeltaSupport
+{
+ private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, BranchingListTableMappingWithRanges.class);
+
+ /**
+ * Used to clean up lists for detached objects.
+ */
+ private static final int FINAL_VERSION = Integer.MAX_VALUE;
+
+ /**
+ * The table of this mapping.
+ */
+ private IDBTable table;
+
+ /**
+ * The type mapping for the value field.
+ */
+ private ITypeMapping typeMapping;
+
+ // --------- SQL strings - see initSQLStrings() -----------------
+ private String sqlSelectChunksPrefix;
+
+ private String sqlOrderByIndex;
+
+ private String sqlInsertEntry;
+
+ private String sqlDeleteEntry;
+
+ private String sqlRemoveEntry;
+
+ private String sqlUpdateIndex;
+
+ private String sqlGetValue;
+
+ private String sqlGetListLastIndex;
+
+ private String sqlClearList;
+
+ private String sqlDeleteList;
+
+ public BranchingListTableMappingWithRanges(IMappingStrategy mappingStrategy, EClass eClass, EStructuralFeature feature)
+ {
+ super(mappingStrategy, eClass, feature);
+ initTable();
+ initSQLStrings();
+ }
+
+ private void initTable()
+ {
+ String tableName = getMappingStrategy().getTableName(getContainingClass(), getFeature());
+ table = getMappingStrategy().getStore().getDBSchema().addTable(tableName);
+
+ IDBField[] dbFields = new IDBField[5];
+
+ dbFields[0] = table.addField(CDODBSchema.LIST_REVISION_ID, DBType.BIGINT);
+ dbFields[1] = table.addField(CDODBSchema.LIST_REVISION_BRANCH, DBType.INTEGER);
+ dbFields[2] = table.addField(CDODBSchema.LIST_REVISION_VERSION_ADDED, DBType.INTEGER);
+ dbFields[3] = table.addField(CDODBSchema.LIST_REVISION_VERSION_REMOVED, DBType.INTEGER);
+ dbFields[4] = table.addField(CDODBSchema.LIST_IDX, DBType.INTEGER);
+
+ // add field for value
+ typeMapping = getMappingStrategy().createValueMapping(getFeature());
+ typeMapping.createDBField(table, CDODBSchema.LIST_VALUE);
+
+ // add table indexes
+ for (IDBField dbField : dbFields)
+ {
+ table.addIndex(Type.NON_UNIQUE, dbField);
+ }
+ }
+
+ public Collection<IDBTable> getDBTables()
+ {
+ return Arrays.asList(table);
+ }
+
+ private void initSQLStrings()
+ {
+ String tableName = getTable().getName();
+
+ // ---------------- read chunks ----------------------------
+ StringBuilder builder = new StringBuilder();
+ builder.append("SELECT "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_VALUE);
+ builder.append(" FROM "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED);
+ builder.append("<=? AND ("); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append(" IS NULL OR "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append(">?)"); //$NON-NLS-1$
+ sqlSelectChunksPrefix = builder.toString();
+
+ sqlOrderByIndex = " ORDER BY " + CDODBSchema.LIST_IDX; //$NON-NLS-1$
+
+ // ----------------- count list size --------------------------
+ builder = new StringBuilder("SELECT count(1) FROM "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED);
+ builder.append("<=? AND ("); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append(" IS NULL OR "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append(">?)"); //$NON-NLS-1$
+ sqlGetListLastIndex = builder.toString();
+
+ // ----------------- insert entry -----------------
+ builder = new StringBuilder("INSERT INTO "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append("("); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_BRANCH);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append(","); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_VALUE);
+ builder.append(") VALUES (?, ?, ?, NULL, ?, ?)"); //$NON-NLS-1$
+ sqlInsertEntry = builder.toString();
+
+ // ----------------- remove current entry -----------------
+ builder = new StringBuilder("UPDATE "); //$NON-NLS-1$
+ builder.append(getTable());
+ builder.append(" SET "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append("=? "); //$NON-NLS-1$
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append(" IS NULL"); //$NON-NLS-1$
+ sqlRemoveEntry = builder.toString();
+
+ // ----------------- delete temporary entry -----------------
+ builder = new StringBuilder("DELETE FROM "); //$NON-NLS-1$
+ builder.append(getTable());
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED);
+ builder.append("=?"); //$NON-NLS-1$
+ sqlDeleteEntry = builder.toString();
+
+ // ----------------- update index -----------------
+ builder = new StringBuilder("UPDATE "); //$NON-NLS-1$
+ builder.append(getTable());
+ builder.append(" SET "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append("=? WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append("=?"); //$NON-NLS-1$
+ sqlUpdateIndex = builder.toString();
+
+ // ----------------- get current value -----------------
+ builder = new StringBuilder("SELECT "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_VALUE);
+ builder.append(" FROM "); //$NON-NLS-1$
+ builder.append(getTable());
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append(" IS NULL"); //$NON-NLS-1$
+ sqlGetValue = builder.toString();
+
+ // ----------- clear list items -------------------------
+ builder = new StringBuilder("UPDATE "); //$NON-NLS-1$
+ builder.append(getTable());
+ builder.append(" SET "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append("=? "); //$NON-NLS-1$
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append(" IS NULL"); //$NON-NLS-1$
+ sqlClearList = builder.toString();
+
+ // ----------- delete temporary list items -------------------------
+ builder = new StringBuilder("DELETE FROM "); //$NON-NLS-1$
+ builder.append(getTable());
+ builder.append(" WHERE "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_BRANCH);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_ADDED);
+ builder.append("=? AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_VERSION_REMOVED);
+ builder.append(" IS NULL"); //$NON-NLS-1$
+ sqlDeleteList = builder.toString();
+ }
+
+ protected final IDBTable getTable()
+ {
+ return table;
+ }
+
+ protected final ITypeMapping getTypeMapping()
+ {
+ return typeMapping;
+ }
+
+ 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.getID(), revision.getBranch().getID(), revision.getVersion());
+ if (listSize == -1)
+ {
+ // list is empty - take shortcut
+ return;
+ }
+
+ // 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}", getContainingClass().getName(), //$NON-NLS-1$
+ getFeature().getName(), revision.getID(), revision.getVersion());
+ }
+
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+ ResultSet resultSet = null;
+
+ try
+ {
+ String sql = sqlSelectChunksPrefix + sqlOrderByIndex;
+ pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.HIGH);
+ pstmt.setLong(1, CDOIDUtil.getLong(revision.getID()));
+ pstmt.setInt(2, revision.getBranch().getID());
+ pstmt.setInt(3, revision.getVersion());
+ pstmt.setInt(4, revision.getVersion());
+
+ if (listChunk != CDORevision.UNCHUNKED)
+ {
+ pstmt.setMaxRows(listChunk); // optimization - don't read unneeded rows.
+ }
+
+ resultSet = pstmt.executeQuery();
+ while ((listChunk == CDORevision.UNCHUNKED || --listChunk >= 0) && resultSet.next())
+ {
+ Object value = typeMapping.readValue(resultSet);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Read value for index {0} from result set: {1}", list.size(), value); //$NON-NLS-1$
+ }
+
+ list.add(value);
+ }
+
+ while (listSize-- >= 0)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Adding UNINITIALIZED for index {0} ", list.size()); //$NON-NLS-1$
+ }
+
+ list.add(InternalCDOList.UNINITIALIZED);
+ }
+ }
+ catch (SQLException ex)
+ {
+ throw new DBException(ex);
+ }
+ finally
+ {
+ DBUtil.close(resultSet);
+ statementCache.releasePreparedStatement(pstmt);
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Reading {4} list values done for feature {0}.{1} of {2}v{3}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), revision.getID(), revision.getVersion(), list.size());
+ }
+ }
+
+ /**
+ * Return the last (maximum) list index. (equals to size-1)
+ *
+ * @param accessor
+ * the accessor to use
+ * @param id
+ * the CDOID of the revision to which the getFeature() list belongs
+ * @param id
+ * the branch ID of the revision
+ * @param version
+ * the revision to which the getFeature() list belongs
+ * @return the last index or <code>-1</code> if the list is empty.
+ */
+ private int getListLastIndex(IDBStoreAccessor accessor, CDOID id, int branchId, int version)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+ ResultSet resultSet = null;
+
+ try
+ {
+ pstmt = statementCache.getPreparedStatement(sqlGetListLastIndex, ReuseProbability.HIGH);
+ pstmt.setLong(1, CDOIDUtil.getLong(id));
+ pstmt.setInt(2, branchId);
+ pstmt.setInt(3, version);
+ pstmt.setInt(4, version);
+
+ resultSet = pstmt.executeQuery();
+ if (!resultSet.next())
+ {
+ throw new DBException("Count expects exactly one result");
+ }
+
+ int result = resultSet.getInt(1) - 1;
+ if (TRACER.isEnabled())
+ {
+ TRACER.trace("Read list last index = " + result); //$NON-NLS-1$
+ }
+
+ return result;
+ }
+ catch (SQLException ex)
+ {
+ throw new DBException(ex);
+ }
+ finally
+ {
+ DBUtil.close(resultSet);
+ statementCache.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}", getContainingClass().getName(), //$NON-NLS-1$
+ getFeature().getName(), chunkReader.getRevision().getID(), chunkReader.getRevision().getVersion());
+ }
+
+ IPreparedStatementCache statementCache = chunkReader.getAccessor().getStatementCache();
+ PreparedStatement pstmt = null;
+ ResultSet resultSet = null;
+
+ try
+ {
+ StringBuilder builder = new StringBuilder(sqlSelectChunksPrefix);
+ if (where != null)
+ {
+ builder.append(" AND "); //$NON-NLS-1$
+ builder.append(where);
+ }
+
+ builder.append(sqlOrderByIndex);
+
+ String sql = builder.toString();
+ pstmt = statementCache.getPreparedStatement(sql, ReuseProbability.LOW);
+ pstmt.setLong(1, CDOIDUtil.getLong(chunkReader.getRevision().getID()));
+ pstmt.setInt(2, chunkReader.getRevision().getBranch().getID());
+ pstmt.setInt(3, chunkReader.getRevision().getVersion());
+ pstmt.setInt(4, chunkReader.getRevision().getVersion());
+
+ resultSet = pstmt.executeQuery();
+
+ Chunk chunk = null;
+ int chunkSize = 0;
+ int chunkIndex = 0;
+ int indexInChunk = 0;
+
+ while (resultSet.next())
+ {
+ Object value = typeMapping.readValue(resultSet);
+
+ 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(), //$NON-NLS-1$
+ chunkSize);
+ }
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Read value for chunk index {0} from result set: {1}", indexInChunk, value); //$NON-NLS-1$
+ }
+
+ chunk.add(indexInChunk++, value);
+ if (indexInChunk == chunkSize)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Chunk finished"); //$NON-NLS-1$
+ }
+
+ chunk = null;
+ indexInChunk = 0;
+ }
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Reading list chunk values done for feature() {0}.{1} of {2}v{3}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), chunkReader.getRevision().getID(), chunkReader
+ .getRevision().getVersion());
+ }
+ }
+ catch (SQLException ex)
+ {
+ throw new DBException(ex);
+ }
+ finally
+ {
+ DBUtil.close(resultSet);
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+
+ public void writeValues(IDBStoreAccessor accessor, InternalCDORevision revision)
+ {
+ // TODO branch stuff
+
+ CDOList values = revision.getList(getFeature());
+
+ int idx = 0;
+ for (Object element : values)
+ {
+ writeValue(accessor, revision, idx++, element);
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Writing done"); //$NON-NLS-1$
+ }
+ }
+
+ protected final void writeValue(IDBStoreAccessor accessor, CDORevision revision, int index, Object value)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER
+ .format(
+ "Writing value for feature {0}.{1} index {2} of {3}v{4} : {5}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), index, revision.getID(), revision.getVersion(),
+ value);
+ }
+
+ addEntry(accessor, revision.getID(), revision.getBranch().getID(), revision.getVersion(), index, value);
+ }
+
+ /**
+ * 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, int branchId, int oldVersion, int newVersion)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmtDeleteTemp = null;
+ PreparedStatement pstmtClear = null;
+
+ try
+ {
+ // delete temporary entries
+ pstmtDeleteTemp = statementCache.getPreparedStatement(sqlDeleteList, ReuseProbability.HIGH);
+ pstmtDeleteTemp.setLong(1, CDOIDUtil.getLong(id));
+ pstmtDeleteTemp.setInt(2, branchId);
+ pstmtDeleteTemp.setInt(3, newVersion);
+
+ int result = CDODBUtil.sqlUpdate(pstmtDeleteTemp, false);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("DeleteList result: {0}", result); //$NON-NLS-1$
+ }
+
+ // clear rest of the list
+ pstmtClear = statementCache.getPreparedStatement(sqlClearList, ReuseProbability.HIGH);
+ pstmtClear.setInt(1, newVersion);
+ pstmtClear.setLong(2, CDOIDUtil.getLong(id));
+ pstmtClear.setInt(3, branchId);
+
+ result = CDODBUtil.sqlUpdate(pstmtClear, false);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("ClearList result: {0}", result); //$NON-NLS-1$
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmtDeleteTemp);
+ statementCache.releasePreparedStatement(pstmtClear);
+ }
+ }
+
+ public void objectDetached(IDBStoreAccessor accessor, CDOID id, long revised)
+ {
+ CDORevision revision = accessor.getTransaction().getRevision(id);
+ int branchId = accessor.getTransaction().getBranch().getID();
+ clearList(accessor, id, branchId, revision.getVersion(), FINAL_VERSION);
+ }
+
+ public void processDelta(final IDBStoreAccessor accessor, final CDOID id, final int branchId, final int oldVersion,
+ final int newVersion, long created, CDOListFeatureDelta delta)
+ {
+ InternalCDORevision originalRevision = (InternalCDORevision)accessor.getTransaction().getRevision(id);
+
+ // IRepository repo = accessor.getStore().getRepository();
+ // InternalCDORevision originalRevision = (InternalCDORevision)repo.getRevisionManager().getRevision(id,
+ // repo.getBranchManager().getBranch(branchId).getHead(), /* chunksize = */0, CDORevision.DEPTH_NONE, true);
+
+ int oldListSize = originalRevision.getList(getFeature()).size();
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("ListTableMapping.processDelta for revision {0} - previous list size: {1}", originalRevision, //$NON-NLS-1$
+ oldListSize);
+ }
+
+ // let the visitor collect the changes
+ ListDeltaVisitor visitor = new ListDeltaVisitor(accessor, originalRevision, branchId, oldVersion, newVersion);
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Processing deltas..."); //$NON-NLS-1$
+ }
+
+ for (CDOFeatureDelta listDelta : delta.getListChanges())
+ {
+ listDelta.accept(visitor);
+ }
+ }
+
+ /**
+ * @author Stefan Winkler
+ */
+ private class ListDeltaVisitor implements CDOFeatureDeltaVisitor
+ {
+ private IDBStoreAccessor accessor;
+
+ private CDOID id;
+
+ private int branchID;
+
+ private int oldVersion;
+
+ private int newVersion;
+
+ private int lastIndex;
+
+ public ListDeltaVisitor(IDBStoreAccessor accessor, InternalCDORevision originalRevision, int targetBranchID,
+ int oldVersion, int newVersion)
+ {
+ this.accessor = accessor;
+ id = originalRevision.getID();
+ branchID = targetBranchID;
+ this.oldVersion = oldVersion;
+ this.newVersion = newVersion;
+ lastIndex = originalRevision.getList(getFeature()).size() - 1;
+ }
+
+ public void visit(CDOMoveFeatureDelta delta)
+ {
+ int fromIdx = delta.getOldPosition();
+ int toIdx = delta.getNewPosition();
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Moving: {0} to {1}", fromIdx, toIdx); //$NON-NLS-1$
+ }
+
+ Object value = getValue(accessor, id, branchID, fromIdx);
+
+ // remove the item
+ removeEntry(accessor, id, branchID, oldVersion, newVersion, fromIdx);
+
+ // adjust indexes and shift either up or down
+ if (fromIdx < toIdx)
+ {
+ moveOneUp(accessor, id, branchID, oldVersion, newVersion, fromIdx + 1, toIdx);
+ }
+ else
+ { // fromIdx > toIdx here
+ moveOneDown(accessor, id, branchID, oldVersion, newVersion, toIdx, fromIdx - 1);
+ }
+
+ // create the item
+ addEntry(accessor, id, branchID, newVersion, toIdx, value);
+ }
+
+ public void visit(CDOAddFeatureDelta delta)
+ {
+ int startIndex = delta.getIndex();
+ int endIndex = lastIndex;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Adding at: {0}", startIndex); //$NON-NLS-1$
+ }
+
+ if (startIndex <= endIndex)
+ {
+ // make room for the new item
+ moveOneDown(accessor, id, branchID, oldVersion, newVersion, startIndex, endIndex);
+ }
+
+ // create the item
+ addEntry(accessor, id, branchID, newVersion, startIndex, delta.getValue());
+
+ ++lastIndex;
+ }
+
+ public void visit(CDORemoveFeatureDelta delta)
+ {
+ int startIndex = delta.getIndex();
+ int endIndex = lastIndex;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Removing at: {0}", startIndex); //$NON-NLS-1$
+ }
+
+ // remove the item
+ removeEntry(accessor, id, branchID, oldVersion, newVersion, startIndex);
+
+ // make room for the new item
+ moveOneUp(accessor, id, branchID, oldVersion, newVersion, startIndex + 1, endIndex);
+
+ --lastIndex;
+ }
+
+ public void visit(CDOSetFeatureDelta delta)
+ {
+ int index = delta.getIndex();
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Setting at: {0}", index); //$NON-NLS-1$
+ }
+
+ // remove the item
+ removeEntry(accessor, id, branchID, oldVersion, newVersion, index);
+
+ // create the item
+ addEntry(accessor, id, branchID, newVersion, index, delta.getValue());
+ }
+
+ public void visit(CDOUnsetFeatureDelta delta)
+ {
+ if (delta.getFeature().isUnsettable())
+ {
+ throw new ImplementationError("Should not be called"); //$NON-NLS-1$
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Unsetting"); //$NON-NLS-1$
+ }
+
+ clearList(accessor, id, branchID, oldVersion, newVersion);
+ lastIndex = -1;
+ }
+
+ public void visit(CDOListFeatureDelta delta)
+ {
+ throw new ImplementationError("Should not be called"); //$NON-NLS-1$
+ }
+
+ public void visit(CDOClearFeatureDelta delta)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Delta Clearing"); //$NON-NLS-1$
+ }
+
+ clearList(accessor, id, branchID, oldVersion, newVersion);
+ lastIndex = -1;
+ }
+
+ public void visit(CDOContainerFeatureDelta delta)
+ {
+ throw new ImplementationError("Should not be called"); //$NON-NLS-1$
+ }
+
+ private void moveOneUp(IDBStoreAccessor accessor, CDOID id, int branchId, int oldVersion, int newVersion,
+ int startIndex, int endIndex)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+
+ try
+ {
+ pstmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH);
+
+ for (int index = startIndex; index <= endIndex; ++index)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp moving: {0} -> {1}", index, index - 1); //$NON-NLS-1$
+ }
+
+ int stmtIndex = 1;
+ pstmt.setInt(stmtIndex++, index - 1);
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, newVersion);
+ pstmt.setInt(stmtIndex++, index);
+
+ int result = CDODBUtil.sqlUpdate(pstmt, false);
+ switch (result)
+ {
+ case 0:
+ Object value = getValue(accessor, id, branchId, index);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp remove: {0}", index); //$NON-NLS-1$
+ }
+
+ removeEntry(accessor, id, branchId, oldVersion, newVersion, index);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp add: {0}", index - 1); //$NON-NLS-1$
+ }
+
+ addEntry(accessor, id, branchId, newVersion, index - 1, value);
+ break;
+
+ case 1:
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp updated: {0} -> {1}", index, index - 1); //$NON-NLS-1$
+ }
+
+ break;
+
+ default:
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneUp Too many results: {0} -> {1}: {2}", index, index + 1, result); //$NON-NLS-1$
+ }
+
+ throw new DBException("Too many results"); //$NON-NLS-1$
+ }
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+
+ private void moveOneDown(IDBStoreAccessor accessor, CDOID id, int branchId, int oldVersion, int newVersion,
+ int startIndex, int endIndex)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+
+ try
+ {
+ pstmt = statementCache.getPreparedStatement(sqlUpdateIndex, ReuseProbability.HIGH);
+
+ for (int index = endIndex; index >= startIndex; --index)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown moving: {0} -> {1}", index, index + 1); //$NON-NLS-1$
+ }
+
+ int stmtIndex = 1;
+ pstmt.setInt(stmtIndex++, index + 1);
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, newVersion);
+ pstmt.setInt(stmtIndex++, index);
+
+ int result = CDODBUtil.sqlUpdate(pstmt, false);
+ switch (result)
+ {
+ case 0:
+ Object value = getValue(accessor, id, branchId, index);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown remove: {0}", index); //$NON-NLS-1$
+ }
+
+ removeEntry(accessor, id, branchId, oldVersion, newVersion, index);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown add: {0}", index + 1); //$NON-NLS-1$
+ }
+
+ addEntry(accessor, id, branchId, newVersion, index + 1, value);
+ break;
+
+ case 1:
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown updated: {0} -> {1}", index, index + 1); //$NON-NLS-1$
+ }
+
+ break;
+
+ default:
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("moveOneDown Too many results: {0} -> {1}: {2}", index, index + 1, result); //$NON-NLS-1$
+ }
+
+ throw new DBException("Too many results"); //$NON-NLS-1$
+ }
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+ }
+
+ private void addEntry(IDBStoreAccessor accessor, CDOID id, int branchId, int version, int index, Object value)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Adding value for feature() {0}.{1} index {2} of {3}b{4}v{5} : {6}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), index, id, branchId, version, value);
+ }
+
+ try
+ {
+ pstmt = statementCache.getPreparedStatement(sqlInsertEntry, ReuseProbability.HIGH);
+
+ int stmtIndex = 1;
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, version);
+ pstmt.setInt(stmtIndex++, index);
+ typeMapping.setValue(pstmt, stmtIndex++, value);
+
+ CDODBUtil.sqlUpdate(pstmt, true);
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ catch (IllegalStateException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+
+ private void removeEntry(IDBStoreAccessor accessor, CDOID id, int branchId, int oldVersion, int newVersion, int index)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), index, id, newVersion);
+ }
+
+ try
+ {
+ // try to delete a temporary entry first
+ pstmt = statementCache.getPreparedStatement(sqlDeleteEntry, ReuseProbability.HIGH);
+
+ int stmtIndex = 1;
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, index);
+ pstmt.setInt(stmtIndex++, newVersion);
+
+ int result = CDODBUtil.sqlUpdate(pstmt, false);
+ if (result == 1)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("removeEntry deleted: {0}", index); //$NON-NLS-1$
+ }
+ }
+ else if (result > 1)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("removeEntry Too many results: {0}: {1}", index, result); //$NON-NLS-1$
+ }
+
+ throw new DBException("Too many results"); //$NON-NLS-1$
+ }
+ else
+ {
+ // no temporary entry found, so mark the entry as removed
+ statementCache.releasePreparedStatement(pstmt);
+ pstmt = statementCache.getPreparedStatement(sqlRemoveEntry, ReuseProbability.HIGH);
+
+ stmtIndex = 1;
+ pstmt.setInt(stmtIndex++, newVersion);
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, index);
+
+ CDODBUtil.sqlUpdate(pstmt, true);
+ }
+ }
+ catch (SQLException e)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4} FAILED {5}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), index, id, newVersion, e.getMessage());
+ }
+
+ throw new DBException(e);
+ }
+ catch (IllegalStateException e)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Removing value for feature() {0}.{1} index {2} of {3}v{4} FAILED {5}", //$NON-NLS-1$
+ getContainingClass().getName(), getFeature().getName(), index, id, newVersion, e.getMessage());
+ }
+
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+ }
+
+ private Object getValue(IDBStoreAccessor accessor, CDOID id, int branchId, int index)
+ {
+ IPreparedStatementCache statementCache = accessor.getStatementCache();
+ PreparedStatement pstmt = null;
+ Object result = null;
+
+ try
+ {
+ pstmt = statementCache.getPreparedStatement(sqlGetValue, ReuseProbability.HIGH);
+ int stmtIndex = 1;
+ pstmt.setLong(stmtIndex++, CDOIDUtil.getLong(id));
+ pstmt.setInt(stmtIndex++, branchId);
+ pstmt.setInt(stmtIndex++, index);
+
+ ResultSet resultSet = pstmt.executeQuery();
+ if (!resultSet.next())
+ {
+ throw new DBException("getValue() expects exactly one result");
+ }
+
+ result = typeMapping.readValue(resultSet);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Read value (index {0}) from result set: {1}", index, result); //$NON-NLS-1$
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new DBException(e);
+ }
+ finally
+ {
+ statementCache.releasePreparedStatement(pstmt);
+ }
+
+ return result;
+ }
+
+ public final boolean queryXRefs(IDBStoreAccessor accessor, String mainTableName, String mainTableWhere,
+ QueryXRefsContext context, String idString)
+ {
+
+ String tableName = getTable().getName();
+ String listJoin = getMappingStrategy().getListJoin("a_t", "l_t");
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("SELECT l_t."); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_REVISION_ID);
+ builder.append(", l_t."); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_VALUE);
+ builder.append(", l_t."); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_IDX);
+ builder.append(" FROM "); //$NON-NLS-1$
+ builder.append(tableName);
+ builder.append(" AS l_t, ");//$NON-NLS-1$
+ builder.append(mainTableName);
+ builder.append(" AS a_t WHERE ");//$NON-NLS-1$
+ builder.append("a_t." + mainTableWhere);//$NON-NLS-1$
+ builder.append(listJoin);
+ builder.append(" AND "); //$NON-NLS-1$
+ builder.append(CDODBSchema.LIST_VALUE);
+ builder.append(" IN "); //$NON-NLS-1$
+ builder.append(idString);
+ String sql = builder.toString();
+
+ ResultSet resultSet = null;
+ Statement stmt = null;
+
+ try
+ {
+ stmt = accessor.getConnection().createStatement();
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Query XRefs (list): {0}", sql);
+ }
+
+ resultSet = stmt.executeQuery(sql);
+ while (resultSet.next())
+ {
+ long idLong = resultSet.getLong(1);
+ CDOID srcId = CDOIDUtil.createLong(idLong);
+ idLong = resultSet.getLong(2);
+ CDOID targetId = CDOIDUtil.createLong(idLong);
+ int idx = resultSet.getInt(3);
+
+ boolean more = context.addXRef(targetId, srcId, (EReference)getFeature(), idx);
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" add XRef to context: src={0}, tgt={1}, idx={2}", srcId, targetId, idx);
+ }
+
+ if (!more)
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(" result limit reached. Ignoring further results.");
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+ catch (SQLException ex)
+ {
+ throw new DBException(ex);
+ }
+ finally
+ {
+ DBUtil.close(resultSet);
+ DBUtil.close(stmt);
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditClassMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditClassMapping.java
index f785e51..381f483 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditClassMapping.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalAuditClassMapping.java
@@ -611,11 +611,14 @@ public class HorizontalAuditClassMapping extends AbstractHorizontalClassMapping
private InternalCDORevision newRevision;
+ private int branchId;
+
public void process(IDBStoreAccessor accessor, InternalCDORevisionDelta delta, long created)
{
this.accessor = accessor;
this.created = created;
id = delta.getID();
+ branchId = delta.getBranch().getID();
oldVersion = delta.getVersion();
if (TRACER.isEnabled())
@@ -668,7 +671,7 @@ public class HorizontalAuditClassMapping extends AbstractHorizontalClassMapping
public void visit(CDOListFeatureDelta delta)
{
IListMappingDeltaSupport listMapping = (IListMappingDeltaSupport)getListMapping(delta.getFeature());
- listMapping.processDelta(accessor, id, oldVersion, oldVersion + 1, created, delta);
+ listMapping.processDelta(accessor, id, branchId, oldVersion, oldVersion + 1, created, delta);
}
public void visit(CDOClearFeatureDelta delta)
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalBranchingClassMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalBranchingClassMapping.java
index 767fb89..5235e36 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalBranchingClassMapping.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalBranchingClassMapping.java
@@ -19,9 +19,19 @@ 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.id.CDOIDUtil;
+import org.eclipse.emf.cdo.common.model.CDOModelUtil;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
import org.eclipse.emf.cdo.common.revision.CDORevisionManager;
+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.CDOSetFeatureDelta;
+import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta;
import org.eclipse.emf.cdo.eresource.EresourcePackage;
import org.eclipse.emf.cdo.server.IRepository;
import org.eclipse.emf.cdo.server.IStoreAccessor.QueryXRefsContext;
@@ -30,13 +40,17 @@ import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
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.IClassMappingAuditSupport;
+import org.eclipse.emf.cdo.server.db.mapping.IClassMappingDeltaSupport;
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.ITypeMapping;
import org.eclipse.emf.cdo.server.internal.db.CDODBSchema;
import org.eclipse.emf.cdo.server.internal.db.DBStore;
import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
import org.eclipse.emf.cdo.spi.common.commit.CDOChangeSetSegment;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
+import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
+import org.eclipse.emf.cdo.spi.server.InternalRepository;
import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBType;
@@ -64,8 +78,101 @@ import java.util.Set;
* @since 3.0
*/
public class HorizontalBranchingClassMapping extends AbstractHorizontalClassMapping implements
- IClassMappingAuditSupport
+ IClassMappingAuditSupport, IClassMappingDeltaSupport
{
+ /**
+ * @author Stefan Winkler
+ */
+ private class FeatureDeltaWriter implements CDOFeatureDeltaVisitor
+ {
+ private IDBStoreAccessor accessor;
+
+ private long created;
+
+ private CDOID id;
+
+ private CDOBranch targetBranch;
+
+ private int oldVersion;
+
+ private int newVersion;
+
+ private InternalCDORevision newRevision;
+
+ public void process(IDBStoreAccessor accessor, InternalCDORevisionDelta delta, long created)
+ {
+ this.accessor = accessor;
+ this.created = created;
+ id = delta.getID();
+ oldVersion = delta.getVersion();
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("FeatureDeltaWriter: old version: {0}, new version: {1}", oldVersion, oldVersion + 1); //$NON-NLS-1$
+ }
+
+ InternalCDORevision originalRevision = (InternalCDORevision)accessor.getStore().getRepository()
+ .getRevisionManager().getRevisionByVersion(id, delta, 0, true);
+
+ newRevision = originalRevision.copy();
+
+ newVersion = oldVersion + 1;
+ targetBranch = accessor.getTransaction().getBranch();
+
+ newRevision.setVersion(newVersion);
+ newRevision.setBranchPoint(targetBranch.getPoint(created));
+
+ // process revision delta tree
+ delta.accept(this);
+
+ long revised = newRevision.getTimeStamp() - 1;
+ reviseOldRevision(accessor, id, delta.getBranch(), revised);
+
+ writeValues(accessor, newRevision);
+ }
+
+ public void visit(CDOMoveFeatureDelta 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(CDOSetFeatureDelta delta)
+ {
+ delta.apply(newRevision);
+ }
+
+ public void visit(CDOUnsetFeatureDelta delta)
+ {
+ delta.apply(newRevision);
+ }
+
+ public void visit(CDOListFeatureDelta delta)
+ {
+ IListMappingDeltaSupport listMapping = (IListMappingDeltaSupport)getListMapping(delta.getFeature());
+ listMapping.processDelta(accessor, id, targetBranch.getID(), oldVersion, newVersion, created, delta);
+ }
+
+ public void visit(CDOClearFeatureDelta delta)
+ {
+ throw new ImplementationError("Should not be called"); //$NON-NLS-1$
+ }
+
+ public void visit(CDOContainerFeatureDelta delta)
+ {
+ delta.apply(newRevision);
+ }
+ }
+
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, HorizontalBranchingClassMapping.class);
private String sqlInsertAttributes;
@@ -84,6 +191,15 @@ public class HorizontalBranchingClassMapping extends AbstractHorizontalClassMapp
private String sqlSelectForChangeSet;
+ private ThreadLocal<FeatureDeltaWriter> deltaWriter = new ThreadLocal<FeatureDeltaWriter>()
+ {
+ @Override
+ protected FeatureDeltaWriter initialValue()
+ {
+ return new FeatureDeltaWriter();
+ }
+ };
+
public HorizontalBranchingClassMapping(AbstractHorizontalMappingStrategy mappingStrategy, EClass eClass)
{
super(mappingStrategy, eClass);
@@ -593,7 +709,7 @@ public class HorizontalBranchingClassMapping extends AbstractHorizontalClassMapp
{
// put new objects into objectTypeMapper
long timeStamp = revision.getTimeStamp();
- HorizontalBranchingMappingStrategy mappingStrategy = (HorizontalBranchingMappingStrategy)getMappingStrategy();
+ AbstractHorizontalMappingStrategy mappingStrategy = (AbstractHorizontalMappingStrategy)getMappingStrategy();
mappingStrategy.putObjectType(accessor, timeStamp, id, getEClass());
}
else if (revision.getVersion() > CDOBranchVersion.FIRST_VERSION)
@@ -873,4 +989,78 @@ public class HorizontalBranchingClassMapping extends AbstractHorizontalClassMapp
return builder.toString();
}
+
+ public void writeRevisionDelta(IDBStoreAccessor accessor, InternalCDORevisionDelta delta, long created,
+ OMMonitor monitor)
+ {
+ monitor.begin();
+
+ try
+ {
+ if (accessor.getTransaction().getBranch() == delta.getBranch())
+ {
+ // same branch -> write delta
+ Async async = null;
+
+ try
+ {
+ async = monitor.forkAsync();
+ FeatureDeltaWriter writer = deltaWriter.get();
+ writer.process(accessor, delta, created);
+ }
+ finally
+ {
+ if (async != null)
+ {
+ async.stop();
+ }
+ }
+ }
+ else
+ {
+ // new branch -> copy revision
+ writeNewBranchRevisionDelta(accessor, delta, created, monitor.fork());
+ }
+
+ }
+ finally
+ {
+ monitor.done();
+ }
+ }
+
+ private void writeNewBranchRevisionDelta(IDBStoreAccessor accessor, InternalCDORevisionDelta delta, long created,
+ OMMonitor monitor)
+ {
+ monitor.begin(2);
+ try
+ {
+ InternalRepository repository = (InternalRepository)accessor.getStore().getRepository();
+
+ InternalCDORevision oldRevision = (InternalCDORevision)accessor.getTransaction().getRevision(delta.getID());
+ if (oldRevision == null)
+ {
+ throw new IllegalStateException("Origin revision not found for " + delta);
+ }
+
+ // Make sure all chunks are loaded
+ for (EStructuralFeature feature : CDOModelUtil.getAllPersistentFeatures(oldRevision.getEClass()))
+ {
+ if (feature.isMany())
+ {
+ repository.ensureChunk(oldRevision, feature, 0, oldRevision.getList(feature).size());
+ }
+ }
+
+ InternalCDORevision newRevision = oldRevision.copy();
+ newRevision.adjustForCommit(accessor.getTransaction().getBranch(), created);
+ delta.apply(newRevision);
+ monitor.worked();
+ writeRevision(accessor, newRevision, false, monitor.fork());
+ }
+ finally
+ {
+ monitor.done();
+ }
+ }
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalBranchingMappingStrategyWithRanges.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalBranchingMappingStrategyWithRanges.java
new file mode 100644
index 0000000..218b1b6
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalBranchingMappingStrategyWithRanges.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2004 - 2010 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
+ */
+package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal;
+
+import org.eclipse.emf.cdo.server.db.mapping.IClassMapping;
+import org.eclipse.emf.cdo.server.db.mapping.IListMapping;
+import org.eclipse.emf.cdo.server.internal.db.CDODBSchema;
+
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EStructuralFeature;
+
+/**
+ * @author Eike Stepper
+ * @since 2.0
+ */
+public class HorizontalBranchingMappingStrategyWithRanges extends AbstractHorizontalMappingStrategy
+{
+ public HorizontalBranchingMappingStrategyWithRanges()
+ {
+ }
+
+ public boolean hasAuditSupport()
+ {
+ return true;
+ }
+
+ public boolean hasBranchingSupport()
+ {
+ return true;
+ }
+
+ public boolean hasDeltaSupport()
+ {
+ return true;
+ }
+
+ @Override
+ public IClassMapping doCreateClassMapping(EClass eClass)
+ {
+ return new HorizontalBranchingClassMapping(this, eClass);
+ }
+
+ @Override
+ public IListMapping doCreateListMapping(EClass containingClass, EStructuralFeature feature)
+ {
+ return new BranchingListTableMappingWithRanges(this, containingClass, feature);
+ }
+
+ @Override
+ public IListMapping doCreateFeatureMapMapping(EClass containingClass, EStructuralFeature feature)
+ {
+ return new BranchingFeatureMapTableMappingWithRanges(this, containingClass, feature);
+ }
+
+ @Override
+ public String getListJoin(String attrTable, String listTable)
+ {
+ String join = super.getListJoin(attrTable, listTable);
+ join += " AND " + listTable + "." + CDODBSchema.LIST_REVISION_VERSION_ADDED;
+ join += "<=" + attrTable + "." + CDODBSchema.ATTRIBUTES_VERSION;
+ join += " AND (" + listTable + "." + CDODBSchema.LIST_REVISION_VERSION_REMOVED;
+ join += " IS NULL OR " + listTable + "." + CDODBSchema.LIST_REVISION_VERSION_REMOVED;
+ join += ">" + attrTable + "." + CDODBSchema.ATTRIBUTES_VERSION;
+ join += ") AND " + attrTable + "." + CDODBSchema.ATTRIBUTES_BRANCH;
+ join += "=" + listTable + "." + CDODBSchema.LIST_REVISION_BRANCH;
+ return join;
+ }
+}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditClassMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditClassMapping.java
index 8c51103..ea62e86 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditClassMapping.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/HorizontalNonAuditClassMapping.java
@@ -472,6 +472,8 @@ public class HorizontalNonAuditClassMapping extends AbstractHorizontalClassMappi
private CDOID newResourceID;
+ private int branchId;
+
public FeatureDeltaWriter()
{
attributeChanges = new ArrayList<Pair<ITypeMapping, Object>>();
@@ -489,6 +491,7 @@ public class HorizontalNonAuditClassMapping extends AbstractHorizontalClassMappi
reset();
id = d.getID();
+ branchId = d.getBranch().getID();
oldVersion = d.getVersion();
int newVersion = oldVersion + 1;
created = c;
@@ -541,7 +544,7 @@ public class HorizontalNonAuditClassMapping extends AbstractHorizontalClassMappi
public void visit(CDOListFeatureDelta delta)
{
IListMappingDeltaSupport listMapping = (IListMappingDeltaSupport)getListMapping(delta.getFeature());
- listMapping.processDelta(accessor, id, oldVersion, oldVersion + 1, created, delta);
+ listMapping.processDelta(accessor, id, branchId, oldVersion, oldVersion + 1, created, delta);
}
public void visit(CDOClearFeatureDelta delta)
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
index fec311a..40ce25a 100644
--- 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
@@ -513,8 +513,8 @@ public class NonAuditFeatureMapTableMapping extends AbstractFeatureMapTableMappi
}
}
- public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int oldVersion, final int newVersion,
- long created, CDOListFeatureDelta listDelta)
+ public void processDelta(final IDBStoreAccessor accessor, final CDOID id, final int branchId, int oldVersion,
+ final int newVersion, long created, CDOListFeatureDelta listDelta)
{
CDOFeatureDeltaVisitor visitor = new CDOFeatureDeltaVisitor()
{
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java
index 08f274c..50423b4 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/NonAuditListTableMapping.java
@@ -195,8 +195,8 @@ public class NonAuditListTableMapping extends AbstractListTableMapping implement
}
}
- public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int oldVersion, final int newVersion,
- long created, CDOListFeatureDelta delta)
+ public void processDelta(final IDBStoreAccessor accessor, final CDOID id, int branchId, int oldVersion,
+ final int newVersion, long created, CDOListFeatureDelta delta)
{
CDOBranchPoint main = accessor.getStore().getRepository().getBranchManager().getMainBranch().getHead();
diff --git a/plugins/org.eclipse.emf.cdo.tests.db/CDO AllTests (H2 branching).launch b/plugins/org.eclipse.emf.cdo.tests.db/CDO AllTests (H2 branching).launch
index 9f0aede..c42118c 100644
--- a/plugins/org.eclipse.emf.cdo.tests.db/CDO AllTests (H2 branching).launch
+++ b/plugins/org.eclipse.emf.cdo.tests.db/CDO AllTests (H2 branching).launch
@@ -12,5 +12,5 @@
<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.AllTestsDBH2Branching"/>
<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&#13;&#10;-Xmx512m"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms256m&#13;&#10;-Xmx1536m"/>
</launchConfiguration>
diff --git a/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2Branching.java b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2Branching.java
index a90c7d7..890dc4d 100644
--- a/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2Branching.java
+++ b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2Branching.java
@@ -11,7 +11,6 @@
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.BranchingTest;
import org.eclipse.emf.cdo.tests.BranchingTestSameSession;
@@ -52,7 +51,8 @@ public class AllTestsDBH2Branching extends DBConfigs
@Override
protected void initConfigSuites(TestSuite parent)
{
- addScenario(parent, COMBINED, H2Branching.ReusableFolder.INSTANCE, JVM, NATIVE);
+ // addScenario(parent, COMBINED, H2Branching.ReusableFolder.INSTANCE, JVM, NATIVE);
+ addScenario(parent, COMBINED, H2Branching.ReusableFolder.RANGE_INSTANCE, JVM, NATIVE);
}
@Override
@@ -85,13 +85,17 @@ public class AllTestsDBH2Branching extends DBConfigs
{
private static final long serialVersionUID = 1L;
- public static final H2Branching INSTANCE = new H2Branching("DBStore: H2 (branching)");
+ public static final H2Branching INSTANCE = new H2Branching("DBStore: H2 (branching)",
+ "org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalBranchingMappingStrategy");
protected transient File dbFolder;
- public H2Branching(String name)
+ private String mappingStrategy;
+
+ public H2Branching(String name, String mappingStrategy)
{
super(name);
+ this.mappingStrategy = mappingStrategy;
}
@Override
@@ -102,10 +106,19 @@ public class AllTestsDBH2Branching extends DBConfigs
props.put(IRepository.Props.SUPPORTING_BRANCHES, "true");
}
+ @SuppressWarnings("unchecked")
@Override
protected IMappingStrategy createMappingStrategy()
{
- return CDODBUtil.createHorizontalMappingStrategy(true, true);
+ try
+ {
+ Class<IMappingStrategy> clazz = (Class<IMappingStrategy>)Class.forName(mappingStrategy);
+ return clazz.newInstance();
+ }
+ catch (Exception ex)
+ {
+ throw WrappedException.wrap(ex);
+ }
}
@Override
@@ -145,7 +158,12 @@ public class AllTestsDBH2Branching extends DBConfigs
{
private static final long serialVersionUID = 1L;
- public static final ReusableFolder INSTANCE = new ReusableFolder("DBStore: H2 (branching)");
+ public static final ReusableFolder INSTANCE = new ReusableFolder("DBStore: H2 (branching)",
+ "org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalBranchingMappingStrategy");
+
+ public static final ReusableFolder RANGE_INSTANCE = new ReusableFolder(
+ "DBStore: H2 (Reusable Folder, branching, range-based mapping strategy)",
+ "org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalBranchingMappingStrategyWithRanges");
private static File reusableFolder;
@@ -153,9 +171,9 @@ public class AllTestsDBH2Branching extends DBConfigs
private transient ArrayList<String> repoNames = new ArrayList<String>();
- public ReusableFolder(String name)
+ public ReusableFolder(String name, String mappingStrategy)
{
- super(name);
+ super(name, mappingStrategy);
}
@Override
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java
index 2d9cbc6..779033b 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java
@@ -126,6 +126,7 @@ public abstract class AllConfigs extends ConfigTestSuite
testClasses.add(RevisionManagerTestClientSide.class);
testClasses.add(BranchingTest.class);
testClasses.add(BranchingTestSameSession.class);
+ testClasses.add(BranchingTestWithCacheClear.class);
testClasses.add(MergingTest.class);
testClasses.add(ViewTest.class);
testClasses.add(TransactionTest.class);
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTest.java
index 7724304..423db2b 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTest.java
@@ -385,6 +385,148 @@ public class BranchingTest extends AbstractCDOTest
session.close();
}
+ public void testCommitAddOrderDetail() throws Exception
+ {
+ CDOSession session = openSession1();
+ CDOBranchManager branchManager = session.getBranchManager();
+
+ // Commit to main branch
+ CDOBranch mainBranch = branchManager.getMainBranch();
+ CDOTransaction transaction = session.openTransaction(mainBranch);
+ assertEquals(mainBranch, transaction.getBranch());
+ assertEquals(CDOBranchPoint.UNSPECIFIED_DATE, transaction.getTimeStamp());
+
+ Product1 product = getModel1Factory().createProduct1();
+ product.setName("CDO");
+
+ OrderDetail orderDetail = getModel1Factory().createOrderDetail();
+ orderDetail.setProduct(product);
+ orderDetail.setPrice(5);
+
+ CDOResource resource = transaction.createResource("/res");
+ resource.getContents().add(product);
+ resource.getContents().add(orderDetail);
+
+ CDOCommitInfo commitInfo = transaction.commit();
+ dumpAll(session);
+ assertEquals(mainBranch, commitInfo.getBranch());
+ long commitTime1 = commitInfo.getTimeStamp();
+ transaction.close();
+
+ // Commit to sub branch
+ CDOBranch subBranch = mainBranch.createBranch("subBranch", commitTime1);
+ transaction = session.openTransaction(subBranch);
+ assertEquals(subBranch, transaction.getBranch());
+ assertEquals(CDOBranchPoint.UNSPECIFIED_DATE, transaction.getTimeStamp());
+
+ resource = transaction.getResource("/res");
+ orderDetail = (OrderDetail)resource.getContents().get(1);
+ assertEquals(5.0f, orderDetail.getPrice());
+ product = orderDetail.getProduct();
+ assertEquals("CDO", product.getName());
+
+ // Modify
+ OrderDetail orderDetail2 = getModel1Factory().createOrderDetail();
+ orderDetail2.setProduct(product);
+ orderDetail2.setPrice(10);
+ resource.getContents().add(0, orderDetail2);
+
+ commitInfo = transaction.commit();
+ dumpAll(session);
+ assertEquals(subBranch, commitInfo.getBranch());
+ long commitTime2 = commitInfo.getTimeStamp();
+
+ transaction.close();
+ closeSession1();
+
+ session = openSession2();
+ branchManager = session.getBranchManager();
+ mainBranch = branchManager.getMainBranch();
+ subBranch = mainBranch.getBranch("subBranch");
+
+ check(session, mainBranch, commitTime1, 5, "CDO");
+ check(session, mainBranch, commitTime2, 5, "CDO");
+ check(session, mainBranch, CDOBranchPoint.UNSPECIFIED_DATE, 5, "CDO");
+
+ check(session, subBranch, commitTime1, 5, "CDO");
+ check(session, subBranch, commitTime2, 5, 10, "CDO");
+ check(session, subBranch, CDOBranchPoint.UNSPECIFIED_DATE, 5, 10, "CDO");
+
+ session.close();
+ }
+
+ public void testCommitRemoveOrderDetail() throws Exception
+ {
+ CDOSession session = openSession1();
+ CDOBranchManager branchManager = session.getBranchManager();
+
+ // Commit to main branch
+ CDOBranch mainBranch = branchManager.getMainBranch();
+ CDOTransaction transaction = session.openTransaction(mainBranch);
+ assertEquals(mainBranch, transaction.getBranch());
+ assertEquals(CDOBranchPoint.UNSPECIFIED_DATE, transaction.getTimeStamp());
+
+ Product1 product = getModel1Factory().createProduct1();
+ product.setName("CDO");
+
+ OrderDetail orderDetail = getModel1Factory().createOrderDetail();
+ orderDetail.setProduct(product);
+ orderDetail.setPrice(5);
+
+ OrderDetail orderDetail2 = getModel1Factory().createOrderDetail();
+ orderDetail2.setProduct(product);
+ orderDetail2.setPrice(10);
+
+ CDOResource resource = transaction.createResource("/res");
+ resource.getContents().add(orderDetail2);
+ resource.getContents().add(product);
+ resource.getContents().add(orderDetail);
+
+ CDOCommitInfo commitInfo = transaction.commit();
+ dumpAll(session);
+ assertEquals(mainBranch, commitInfo.getBranch());
+ long commitTime1 = commitInfo.getTimeStamp();
+ transaction.close();
+
+ // Commit to sub branch
+ CDOBranch subBranch = mainBranch.createBranch("subBranch", commitTime1);
+ transaction = session.openTransaction(subBranch);
+ assertEquals(subBranch, transaction.getBranch());
+ assertEquals(CDOBranchPoint.UNSPECIFIED_DATE, transaction.getTimeStamp());
+
+ resource = transaction.getResource("/res");
+ orderDetail = (OrderDetail)resource.getContents().get(2);
+ assertEquals(5.0f, orderDetail.getPrice());
+ product = orderDetail.getProduct();
+ assertEquals("CDO", product.getName());
+
+ // Modify
+ resource.getContents().remove(product.getOrderDetails().remove(1));
+
+ commitInfo = transaction.commit();
+ dumpAll(session);
+ assertEquals(subBranch, commitInfo.getBranch());
+ long commitTime2 = commitInfo.getTimeStamp();
+
+ transaction.close();
+ closeSession1();
+
+ session = openSession2();
+ branchManager = session.getBranchManager();
+ mainBranch = branchManager.getMainBranch();
+ subBranch = mainBranch.getBranch("subBranch");
+
+ check(session, mainBranch, commitTime1, 5, 10, "CDO");
+ check(session, mainBranch, commitTime2, 5, 10, "CDO");
+ check(session, mainBranch, CDOBranchPoint.UNSPECIFIED_DATE, 5, 10, "CDO");
+
+ check(session, subBranch, commitTime1, 5, 10, "CDO");
+ check(session, subBranch, commitTime2, 5, "CDO");
+ check(session, subBranch, CDOBranchPoint.UNSPECIFIED_DATE, 5, "CDO");
+
+ session.close();
+ }
+
public void _testDetachExisting() throws Exception
{
CDOSession session = openSession1();
@@ -519,6 +661,7 @@ public class BranchingTest extends AbstractCDOTest
{
CDOView view = session.openView(branch, timeStamp);
CDOResource resource = view.getResource("/res");
+ assertEquals(2, resource.getContents().size());
dumpAll(session);
OrderDetail orderDetail = (OrderDetail)resource.getContents().get(1);
@@ -533,6 +676,29 @@ public class BranchingTest extends AbstractCDOTest
view.close();
}
+ private void check(CDOSession session, CDOBranch branch, long timeStamp, float price, float price2, String name)
+ {
+ CDOView view = session.openView(branch, timeStamp);
+ CDOResource resource = view.getResource("/res");
+ assertEquals(3, resource.getContents().size());
+
+ dumpAll(session);
+ OrderDetail orderDetail2 = (OrderDetail)resource.getContents().get(0);
+ OrderDetail orderDetail = (OrderDetail)resource.getContents().get(2);
+
+ dumpAll(session);
+ assertEquals(price, orderDetail.getPrice());
+ assertEquals(price2, orderDetail2.getPrice());
+
+ Product1 product = orderDetail.getProduct();
+ Product1 product2 = orderDetail2.getProduct();
+ dumpAll(session);
+ assertEquals(name, product.getName());
+ assertEquals(name, product2.getName());
+
+ view.close();
+ }
+
public void testDetachWithoutRevision() throws Exception
{
CDOSession session = openSession1();
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTestWithCacheClear.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTestWithCacheClear.java
new file mode 100644
index 0000000..3291054
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTestWithCacheClear.java
@@ -0,0 +1,15 @@
+package org.eclipse.emf.cdo.tests;
+
+
+/**
+ * @author Eike Stepper
+ */
+public class BranchingTestWithCacheClear extends BranchingTest
+{
+ @Override
+ protected void closeSession1()
+ {
+ super.closeSession1();
+ clearCache(getRepository().getRevisionManager());
+ }
+}