diff --git a/src/main/java/org/caosdb/server/database/DatabaseUtils.java b/src/main/java/org/caosdb/server/database/DatabaseUtils.java index 5280de17d91982db0481105af8c9ca760db1384f..8f4a59bb1057aa7c9621a8e174295ee2264081d5 100644 --- a/src/main/java/org/caosdb/server/database/DatabaseUtils.java +++ b/src/main/java/org/caosdb/server/database/DatabaseUtils.java @@ -25,16 +25,21 @@ package org.caosdb.server.database; import com.google.common.base.Objects; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.caosdb.server.database.proto.FlatProperty; import org.caosdb.server.database.proto.ProtoProperty; import org.caosdb.server.database.proto.SparseEntity; import org.caosdb.server.database.proto.VerySparseEntity; import org.caosdb.server.datatype.AbstractCollectionDatatype; import org.caosdb.server.datatype.CollectionValue; +import org.caosdb.server.datatype.GenericValue; import org.caosdb.server.datatype.IndexedSingleValue; import org.caosdb.server.datatype.ReferenceValue; import org.caosdb.server.datatype.SingleValue; @@ -175,9 +180,9 @@ public class DatabaseUtils { } public static List<FlatProperty> parsePropertyResultset(final ResultSet rs) throws SQLException { - final ArrayList<FlatProperty> ret = new ArrayList<FlatProperty>(); + final List<FlatProperty> ret = new LinkedList<>(); while (rs.next()) { - final FlatProperty fp = new FlatProperty(); + FlatProperty fp = new FlatProperty(); fp.id = rs.getInt("PropertyID"); fp.value = bytes2UTF8(rs.getBytes("PropertyValue")); fp.status = bytes2UTF8(rs.getBytes("PropertyStatus")); @@ -354,27 +359,120 @@ public class DatabaseUtils { } } - public static ArrayList<Property> parseFromProtoProperties(final List<ProtoProperty> protos) { + public static ArrayList<Property> parseFromProtoProperties( + EntityInterface entity, List<ProtoProperty> protos) { final ArrayList<Property> ret = new ArrayList<Property>(); for (final ProtoProperty pp : protos) { - final Property property = parseFlatProperty(pp.property); - parseFromFlatProperties(property.getProperties(), pp.subProperties); - ret.add(property); + if (pp.id.equals(entity.getId().toInteger())) { + if (pp.value != null) { + entity.setValue(new GenericValue(pp.value)); + } + if (pp.collValues != null) { + CollectionValue value = new CollectionValue(); + for (Object next : pp.collValues) { + if (next == null) { + value.add(null); + } else { + value.add(new GenericValue(next.toString())); + } + } + entity.setValue(value); + } + + } else { + final Property property = parseFlatProperty(pp); + // parseFromFlatProperties(property.getProperties(), pp.subProperties); + ret.add(property); + } } return ret; } private static void parseFromFlatProperties( - final List<Property> properties, final List<FlatProperty> props) { - for (final FlatProperty fp : props) { - final Property property = parseFlatProperty(fp); - properties.add(property); - } + final List<Property> properties, final List<ProtoProperty> props) { + if (props != null) + for (final ProtoProperty fp : props) { + final Property property = parseFlatProperty(fp); + parseFromFlatProperties(properties, fp.subProperties); + properties.add(property); + } } - private static Property parseFlatProperty(final FlatProperty fp) { + private static Property parseFlatProperty(final ProtoProperty fp) { final Property property = new Property(new RetrieveEntity()); - property.parseFlatProperty(fp); + property.parseProtoProperty(fp); return property; } + + @SuppressWarnings("unchecked") + public static LinkedList<ProtoProperty> transformToDeepPropertyTree( + List<ProtoProperty> properties) { + LinkedList<ProtoProperty> result = new LinkedList<>(); + Map<String, ProtoProperty> replacements = new HashMap<>(); + for (ProtoProperty pp : properties) { + if (pp.status.equals(StatementStatus.REPLACEMENT.toString())) { + replacements.put(pp.value, null); + } + } + Iterator<ProtoProperty> iterator = properties.iterator(); + while (iterator.hasNext()) { + ProtoProperty pp = iterator.next(); + if (replacements.containsKey(pp.id.toString())) { + ProtoProperty other = replacements.get(pp.id.toString()); + // handle collection values + if (other != null) { + if (other.collValues == null) { + other.collValues = new LinkedList<>(); + Entry<Integer, String> obj = new SimpleEntry<>(other.idx, other.value); + other.collValues.add(obj); + other.value = null; + } + other.collValues.add(new SimpleEntry<>(pp.idx, pp.value)); + } else { + replacements.put(pp.id.toString(), pp); + } + iterator.remove(); + } + } + for (ProtoProperty pp : properties) { + if (pp.status.equals(StatementStatus.REPLACEMENT.toString())) { + replace(pp, replacements.get(pp.value)); + } + if (pp.collValues != null) { + // sort + pp.collValues.sort( + (x, y) -> { + return ((Entry<Integer, String>) x).getKey() - ((Entry<Integer, String>) y).getKey(); + }); + pp.collValues = + pp.collValues + .stream() + .map((x) -> (Object) ((Entry<Integer, String>) x).getValue()) + .toList(); + } + result.add(pp); + } + return result; + } + + private static void replace(ProtoProperty pp, ProtoProperty replacement) { + if (replacement == null) { + throw new NullPointerException("Replacement was null"); + // // entity has been deleted (we are processing properties of an old entity version) + // p.setValue(null); + // p.addWarning( + // new Message( + // "The referenced entity has been deleted in the mean time and is not longer + // available.")); + // return; + } + pp.desc = replacement.desc; + pp.name = replacement.name; + pp.type = replacement.type; + pp.collection = replacement.collection; + pp.value = replacement.value; + pp.collValues = replacement.collValues; + pp.status = replacement.status; + pp.subProperties = replacement.subProperties; + } } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java index 6bcfeac05cd3631f05615d26741c47f4c5c1bf63..b0c5d5b7ba65054747d9d16c6cf82d6d29cc246e 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java @@ -26,7 +26,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; -import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; @@ -44,29 +44,23 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev private static final String stmtStr = "call retrieveEntityProperties(?,?,?)"; private static final String stmtStr2 = "call retrieveOverrides(?,?,?)"; + private static final String isCollectionStmt = "call isCollection(?,?)"; @Override - public ArrayList<ProtoProperty> execute(final EntityID entity, final String version) + public LinkedList<ProtoProperty> execute(final EntityID entity, final String version) throws TransactionException { try { final PreparedStatement prepareStatement = prepareStatement(stmtStr); - final List<FlatProperty> props = + final List<ProtoProperty> propsStage1 = retrieveFlatPropertiesStage1(0, entity.toInteger(), version, prepareStatement); - final ArrayList<ProtoProperty> protos = new ArrayList<ProtoProperty>(); - for (final FlatProperty p : props) { - final ProtoProperty proto = new ProtoProperty(); - proto.property = p; + for (final ProtoProperty p : propsStage1) { - final List<FlatProperty> subProps = + p.subProperties = retrieveFlatPropertiesStage1(entity.toInteger(), p.id, version, prepareStatement); - - proto.subProperties = subProps; - - protos.add(proto); } - return protos; + return DatabaseUtils.transformToDeepPropertyTree(propsStage1); } catch (final SQLException e) { throw new TransactionException(e); } catch (final ConnectionException e) { @@ -74,68 +68,76 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev } } - private List<FlatProperty> retrieveFlatPropertiesStage1( + private List<ProtoProperty> retrieveFlatPropertiesStage1( final Integer domain, final Integer entity, final String version, final PreparedStatement stmt) throws SQLException, ConnectionException { - ResultSet rs = null; - try { - if (domain != null && domain >= 0) { - stmt.setInt(1, domain); - } else { - stmt.setInt(1, 0); - } + if (domain != null && domain >= 0) { + stmt.setInt(1, domain); + } else { + stmt.setInt(1, 0); + } - stmt.setInt(2, entity); - if (version == null) { - stmt.setNull(3, Types.VARBINARY); - } else { - stmt.setString(3, version); - } + stmt.setInt(2, entity); + if (version == null) { + stmt.setNull(3, Types.VARBINARY); + } else { + stmt.setString(3, version); + } - final long t1 = System.currentTimeMillis(); - rs = stmt.executeQuery(); + final long t1 = System.currentTimeMillis(); + + try (ResultSet rs = stmt.executeQuery()) { final long t2 = System.currentTimeMillis(); addMeasurement(this.getClass().getSimpleName() + ".retrieveFlatPropertiesStage1", t2 - t1); final List<FlatProperty> props = DatabaseUtils.parsePropertyResultset(rs); - final PreparedStatement stmt2 = prepareStatement(stmtStr2); + final PreparedStatement retrieveOverrides = prepareStatement(stmtStr2); - retrieveOverrides(domain, entity, version, stmt2, props); + retrieveOverrides(domain, entity, version, retrieveOverrides, props); - return props; - } finally { - if (rs != null && !rs.isClosed()) { - rs.close(); - } + PreparedStatement isCollectionStatement = prepareStatement(isCollectionStmt); + + return handleCollectionValues(props, isCollectionStatement); } } + public List<ProtoProperty> handleCollectionValues( + List<FlatProperty> props, PreparedStatement isCollectionStatement) throws SQLException { + + List<ProtoProperty> result = new LinkedList<>(); + for (FlatProperty fp : props) { + result.add(new ProtoProperty(fp)); + } + return result; + } + private void retrieveOverrides( final Integer domain, final Integer entity, final String version, - final PreparedStatement stmt2, + final PreparedStatement retrieveOverrides, final List<FlatProperty> props) throws SQLException { ResultSet rs = null; try { - stmt2.setInt(1, domain); - stmt2.setInt(2, entity); + retrieveOverrides.setInt(1, domain); + retrieveOverrides.setInt(2, entity); if (version == null) { - stmt2.setNull(3, Types.VARBINARY); + retrieveOverrides.setNull(3, Types.VARBINARY); } else { - stmt2.setString(3, version); + retrieveOverrides.setString(3, version); } final long t1 = System.currentTimeMillis(); - rs = stmt2.executeQuery(); + rs = retrieveOverrides.executeQuery(); final long t2 = System.currentTimeMillis(); addMeasurement(this.getClass().getSimpleName() + ".retrieveOverrides", t2 - t1); DatabaseUtils.parseOverrides(props, rs); + } finally { if (rs != null && !rs.isClosed()) { rs.close(); diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java index dc1d5aa09653c9a4905dcc456a7037f4009bb838..69fc9e70e03e6060b639d757d97a26d796ee52b5 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java +++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java @@ -22,12 +22,12 @@ */ package org.caosdb.server.database.backend.interfaces; -import java.util.ArrayList; +import java.util.LinkedList; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.ProtoProperty; import org.caosdb.server.entity.EntityID; public interface RetrievePropertiesImpl extends BackendTransactionImpl { - public ArrayList<ProtoProperty> execute(EntityID id, String version) throws TransactionException; + public LinkedList<ProtoProperty> execute(EntityID id, String version) throws TransactionException; } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java index 726382f28c59a45a647098c3d227288fa1fabff6..5cd7ace12f8a1bbd73f1305a0a41f852df5c596e 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java @@ -24,7 +24,8 @@ */ package org.caosdb.server.database.backend.transaction; -import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.caosdb.server.caching.Cache; import org.caosdb.server.database.CacheableBackendTransaction; @@ -37,11 +38,11 @@ import org.caosdb.server.entity.Role; import org.caosdb.server.entity.wrapper.Property; public class RetrieveProperties - extends CacheableBackendTransaction<String, ArrayList<ProtoProperty>> { + extends CacheableBackendTransaction<String, LinkedList<ProtoProperty>> { private final EntityInterface entity; public static final String CACHE_REGION = "BACKEND_EntityProperties"; - private static final ICacheAccess<String, ArrayList<ProtoProperty>> cache = + private static final ICacheAccess<String, LinkedList<ProtoProperty>> cache = Cache.getCache(CACHE_REGION); /** @@ -61,40 +62,45 @@ public class RetrieveProperties } @Override - public ArrayList<ProtoProperty> executeNoCache() throws TransactionException { + public LinkedList<ProtoProperty> executeNoCache() throws TransactionException { final RetrievePropertiesImpl t = getImplementation(RetrievePropertiesImpl.class); return t.execute(this.entity.getId(), this.entity.getVersion().getId()); } @Override - protected void process(final ArrayList<ProtoProperty> t) throws TransactionException { + protected void process(final LinkedList<ProtoProperty> t) throws TransactionException { this.entity.getProperties().clear(); - final ArrayList<Property> props = DatabaseUtils.parseFromProtoProperties(t); + final List<Property> props = DatabaseUtils.parseFromProtoProperties(this.entity, t); + this.entity.getProperties().addAll(props); - for (final Property p : props) { + retrieveSparseSubproperties(props); + } + + private void retrieveSparseSubproperties(List<Property> properties) { + for (final Property p : properties) { // retrieve sparse properties stage 1 + + // handle corner case where a record type is used as a property with an overridden name. + boolean isNameOverride = p.isNameOverride(); + String overrideName = p.getName(); + p.setNameOverride(false); + final RetrieveSparseEntity t1 = new RetrieveSparseEntity(p); execute(t1); + String originalName = p.getName(); + if (isNameOverride) { + p.setNameOverride(isNameOverride); + p.setName(overrideName); + } - // add default data type for record types if (!p.hasDatatype() && p.getRole() == Role.RecordType) { - p.setDatatype(p.getName()); + p.setDatatype(originalName); } // retrieve sparse properties stage 2 - for (final EntityInterface subP : p.getProperties()) { - final RetrieveSparseEntity t2 = new RetrieveSparseEntity(subP); - execute(t2); - - // add default data type for record types - if (!subP.hasDatatype() && subP.getRole() == Role.RecordType) { - subP.setDatatype(subP.getName()); - } - } + retrieveSparseSubproperties(p.getProperties()); } - - DatabaseUtils.transformToDeepPropertyTree(this.entity, props); } @Override diff --git a/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java b/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java index 44acaa024513d3f4b8d89c3f59bda9baf9de3044..898565aca9e7e346f76ff60b9fc45ea0b1547331 100644 --- a/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java +++ b/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java @@ -25,9 +25,22 @@ package org.caosdb.server.database.proto; import java.io.Serializable; import java.util.List; -public class ProtoProperty implements Serializable { +public class ProtoProperty extends FlatProperty implements Serializable { + + public ProtoProperty(FlatProperty fp) { + id = fp.id; + value = fp.value; + status = fp.status; + idx = fp.idx; + name = fp.name; + desc = fp.desc; + type = fp.type; + collection = fp.collection; + } + + public ProtoProperty() {} private static final long serialVersionUID = 7731301985162924975L; - public List<FlatProperty> subProperties = null; - public FlatProperty property = null; + public List<ProtoProperty> subProperties = null; + public List<Object> collValues = null; } diff --git a/src/main/java/org/caosdb/server/entity/wrapper/Property.java b/src/main/java/org/caosdb/server/entity/wrapper/Property.java index efa1a6857a8dec0b3d7dcc45ec689d5b3bc0f745..9268d7f3cb327a70a4cca872657d72e2779fc344 100644 --- a/src/main/java/org/caosdb/server/entity/wrapper/Property.java +++ b/src/main/java/org/caosdb/server/entity/wrapper/Property.java @@ -25,11 +25,13 @@ package org.caosdb.server.entity.wrapper; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; -import org.caosdb.server.database.proto.FlatProperty; +import org.caosdb.server.database.proto.ProtoProperty; import org.caosdb.server.datatype.AbstractCollectionDatatype; +import org.caosdb.server.datatype.CollectionValue; import org.caosdb.server.datatype.GenericValue; import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.xml.PropertyToElementStrategy; public class Property extends EntityWrapper { @@ -72,29 +74,45 @@ public class Property extends EntityWrapper { return this; } - public Property parseFlatProperty(final FlatProperty fp) { - setId(new EntityID(fp.id)); - setStatementStatus(fp.status); - setPIdx(fp.idx); - if (fp.name != null) { - setName(fp.name); + public Property parseProtoProperty(final ProtoProperty pp) { + setId(new EntityID(pp.id)); + setStatementStatus(pp.status); + setPIdx(pp.idx); + if (pp.name != null) { + setName(pp.name); setNameOverride(true); } - if (fp.desc != null) { - setDescription(fp.desc); + if (pp.desc != null) { + setDescription(pp.desc); setDescOverride(true); } - if (fp.type != null) { - if (fp.collection != null) { + if (pp.type != null) { + if (pp.collection != null) { this.setDatatype( - AbstractCollectionDatatype.collectionDatatypeFactory(fp.collection, fp.type)); + AbstractCollectionDatatype.collectionDatatypeFactory(pp.collection, pp.type)); } else { - this.setDatatype(fp.type); + this.setDatatype(pp.type); } setDatatypeOverride(true); } - if (fp.value != null) { - setValue(new GenericValue(fp.value)); + if (pp.value != null) { + setValue(new GenericValue(pp.value)); + } + if (pp.collValues != null) { + CollectionValue value = new CollectionValue(); + for (Object next : pp.collValues) { + if (next == null) { + value.add(null); + } else { + value.add(new GenericValue(next.toString())); + } + } + setValue(value); + } + if (pp.subProperties != null) { + for (ProtoProperty subP : pp.subProperties) { + this.addProperty(new Property(new RetrieveEntity()).parseProtoProperty(subP)); + } } return this; } diff --git a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java index 7f6e6dcae9152091b854007149b0793cbff45542..35d94aa79fd62a04f8b0dae1a17829a1106c2d97 100644 --- a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java +++ b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java @@ -85,9 +85,7 @@ public class EntityToElementStrategy implements ToElementStrategy { // @review Florian Spreckelsen 2022-03-22 - if (entity.getEntityACL() != null) { - element.addContent(entity.getEntityACL().getPermissionsFor(SecurityUtils.getSubject())); - } + addPermissions(entity, element); if (serializeFieldStrategy.isToBeSet("id") && entity.hasId()) { element.setAttribute("id", entity.getId().toString()); } @@ -135,6 +133,12 @@ public class EntityToElementStrategy implements ToElementStrategy { } } + protected void addPermissions(EntityInterface entity, Element element) { + if (entity.getEntityACL() != null) { + element.addContent(entity.getEntityACL().getPermissionsFor(SecurityUtils.getSubject())); + } + } + /** * Set the value of the entity. * diff --git a/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java index 23fd8a553e66df23349c20e8539e3154f3e2ac0a..7109a0d8d5312dcb7cbdecc587dc28fe0a4146ba 100644 --- a/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java +++ b/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java @@ -35,6 +35,11 @@ public class PropertyToElementStrategy extends EntityToElementStrategy { super("Property"); } + @Override + protected void addPermissions(EntityInterface entity, Element element) { + // don't + } + @Override public Element addToElement( final EntityInterface entity, diff --git a/src/test/java/org/caosdb/server/database/InsertTest.java b/src/test/java/org/caosdb/server/database/InsertTest.java index 524e7addd20581db349082f91206605a97bdee8e..59aa6f55084edacc93ad371e5f55b16e72a940f5 100644 --- a/src/test/java/org/caosdb/server/database/InsertTest.java +++ b/src/test/java/org/caosdb/server/database/InsertTest.java @@ -124,7 +124,7 @@ public class InsertTest { final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>(); final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>(); - new LinkedList<EntityInterface>(); + deriveStage1Inserts(stage1Inserts, r); assertEquals(7, stage1Inserts.size()); @@ -166,34 +166,40 @@ public class InsertTest { assertEquals(Role.Property, stage2Inserts.get(0).getRole()); assertEquals(new EntityID(3), stage2Inserts.get(0).getId()); assertEquals("V3", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString()); + assertEquals(new EntityID(2), stage2Inserts.get(0).getDomain()); assertFalse(stage2Inserts.get(0).hasReplacement()); assertEquals(Role.Property, stage2Inserts.get(1).getRole()); assertEquals(new EntityID(5), stage2Inserts.get(1).getId()); assertEquals("V5", ((SingleValue) stage2Inserts.get(1).getValue()).toDatabaseString()); assertTrue(stage2Inserts.get(1).hasReplacement()); + assertEquals(new EntityID(4), stage2Inserts.get(1).getDomain()); assertEquals(stage1Inserts.get(3), stage2Inserts.get(1).getReplacement()); assertEquals(Role.Property, stage2Inserts.get(2).getRole()); assertEquals(new EntityID(6), stage2Inserts.get(2).getId()); assertEquals("V6", ((SingleValue) stage2Inserts.get(2).getValue()).toDatabaseString()); + assertEquals(new EntityID(5), stage2Inserts.get(2).getDomain()); assertFalse(stage2Inserts.get(2).hasReplacement()); assertEquals(Role.Property, stage2Inserts.get(3).getRole()); assertEquals(new EntityID(8), stage2Inserts.get(3).getId()); assertEquals("V8", ((SingleValue) stage2Inserts.get(3).getValue()).toDatabaseString()); assertTrue(stage2Inserts.get(3).hasReplacement()); + assertEquals(new EntityID(7), stage2Inserts.get(3).getDomain()); assertEquals(stage1Inserts.get(5), stage2Inserts.get(3).getReplacement()); assertEquals(Role.Property, stage2Inserts.get(4).getRole()); assertEquals(new EntityID(9), stage2Inserts.get(4).getId()); assertEquals("V9", ((SingleValue) stage2Inserts.get(4).getValue()).toDatabaseString()); assertTrue(stage2Inserts.get(4).hasReplacement()); + assertEquals(new EntityID(8), stage2Inserts.get(4).getDomain()); assertEquals(stage1Inserts.get(6), stage2Inserts.get(4).getReplacement()); assertEquals(Role.Property, stage2Inserts.get(5).getRole()); assertEquals(new EntityID(10), stage2Inserts.get(5).getId()); assertEquals("V10", ((SingleValue) stage2Inserts.get(5).getValue()).toDatabaseString()); + assertEquals(new EntityID(9), stage2Inserts.get(5).getDomain()); assertFalse(stage2Inserts.get(5).hasReplacement()); }