Search in sources :

Example 1 with RelationshipDirection

use of org.neo4j.storageengine.api.RelationshipDirection in project neo4j by neo4j.

the class RelationshipChangesForNodeTest method shouldReportHasRelationshipsOfType.

@Test
void shouldReportHasRelationshipsOfType() {
    // given
    int type = 1;
    RelationshipChangesForNode changes = createRelationshipChangesForNode(ADD, INSTANCE);
    assertThat(changes.hasRelationships(type)).isFalse();
    long relId = 123;
    RelationshipDirection[] directions = new RelationshipDirection[] { OUTGOING, INCOMING, LOOP };
    for (int i = 0; i < directions.length; i++) {
        // when/then
        changes.addRelationship(relId + i, type, directions[i]);
        assertThat(changes.hasRelationships(type)).isTrue();
    }
    for (RelationshipDirection direction : directions) {
        // when/then
        assertThat(changes.hasRelationships(type)).isTrue();
        changes.removeRelationship(relId++, type, direction);
    }
    // and then
    assertThat(changes.hasRelationships(type)).isFalse();
}
Also used : RelationshipDirection(org.neo4j.storageengine.api.RelationshipDirection) RelationshipChangesForNode.createRelationshipChangesForNode(org.neo4j.kernel.impl.api.state.RelationshipChangesForNode.createRelationshipChangesForNode) Test(org.junit.jupiter.api.Test)

Example 2 with RelationshipDirection

use of org.neo4j.storageengine.api.RelationshipDirection in project neo4j by neo4j.

the class RelationshipChangesForNodeTest method shouldVisitRelationshipIds.

@Test
void shouldVisitRelationshipIds() {
    // given
    RelationshipChangesForNode changes = createRelationshipChangesForNode(REMOVE, INSTANCE);
    MutableIntObjectMap<Map<RelationshipDirection, MutableLongSet>> expected = IntObjectMaps.mutable.empty();
    MutableLongSet allExpected = LongSets.mutable.empty();
    for (int id = 0; id < 100; id++) {
        int type = random.nextInt(5);
        RelationshipDirection direction = random.nextBoolean() ? random.nextBoolean() ? OUTGOING : INCOMING : LOOP;
        changes.addRelationship(id, type, direction);
        expected.getIfAbsentPut(type, HashMap::new).computeIfAbsent(direction, d -> LongSets.mutable.empty()).add(id);
        allExpected.add(id);
    }
    // when
    MutableLongSet allChangedIds = LongSets.mutable.empty();
    changes.visitIds(allChangedIds::add);
    // then
    assertThat(allChangedIds).isEqualTo(allExpected);
    // and when
    changes.visitIdsSplit(typeIds -> {
        Map<RelationshipDirection, MutableLongSet> dirMap = expected.remove(typeIds.type());
        visitExpectedIds(typeIds, dirMap, OUTGOING, RelationshipModifications.NodeRelationshipTypeIds::out);
        visitExpectedIds(typeIds, dirMap, INCOMING, RelationshipModifications.NodeRelationshipTypeIds::in);
        visitExpectedIds(typeIds, dirMap, LOOP, RelationshipModifications.NodeRelationshipTypeIds::loop);
        assertThat(dirMap).isEmpty();
        return false;
    }, RelationshipModifications.noAdditionalDataDecorator());
    assertThat(expected).isEmpty();
}
Also used : RelationshipModifications(org.neo4j.storageengine.api.txstate.RelationshipModifications) RandomExtension(org.neo4j.test.extension.RandomExtension) OUTGOING(org.neo4j.storageengine.api.RelationshipDirection.OUTGOING) Direction(org.neo4j.graphdb.Direction) Assertions.assertThat(org.assertj.core.api.Assertions.assertThat) PrimitiveLongCollections.asArray(org.neo4j.collection.PrimitiveLongCollections.asArray) MutableIntObjectMap(org.eclipse.collections.api.map.primitive.MutableIntObjectMap) HashMap(java.util.HashMap) Function(java.util.function.Function) RelationshipDirection(org.neo4j.storageengine.api.RelationshipDirection) IntObjectMaps(org.eclipse.collections.impl.factory.primitive.IntObjectMaps) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) Inject(org.neo4j.test.extension.Inject) RandomRule(org.neo4j.test.rule.RandomRule) Map(java.util.Map) ADD(org.neo4j.kernel.impl.api.state.RelationshipChangesForNode.DiffStrategy.ADD) INCOMING(org.neo4j.storageengine.api.RelationshipDirection.INCOMING) LOOP(org.neo4j.storageengine.api.RelationshipDirection.LOOP) REMOVE(org.neo4j.kernel.impl.api.state.RelationshipChangesForNode.DiffStrategy.REMOVE) MutableLongSet(org.eclipse.collections.api.set.primitive.MutableLongSet) Test(org.junit.jupiter.api.Test) LongIterator(org.eclipse.collections.api.iterator.LongIterator) RelationshipChangesForNode.createRelationshipChangesForNode(org.neo4j.kernel.impl.api.state.RelationshipChangesForNode.createRelationshipChangesForNode) INSTANCE(org.neo4j.memory.EmptyMemoryTracker.INSTANCE) LongSets(org.eclipse.collections.impl.factory.primitive.LongSets) MutableLongSet(org.eclipse.collections.api.set.primitive.MutableLongSet) RelationshipDirection(org.neo4j.storageengine.api.RelationshipDirection) HashMap(java.util.HashMap) RelationshipChangesForNode.createRelationshipChangesForNode(org.neo4j.kernel.impl.api.state.RelationshipChangesForNode.createRelationshipChangesForNode) MutableIntObjectMap(org.eclipse.collections.api.map.primitive.MutableIntObjectMap) HashMap(java.util.HashMap) Map(java.util.Map) Test(org.junit.jupiter.api.Test)

Example 3 with RelationshipDirection

use of org.neo4j.storageengine.api.RelationshipDirection in project neo4j by neo4j.

the class TransactionRecordState method extractCommands.

@Override
public void extractCommands(Collection<StorageCommand> commands, MemoryTracker memoryTracker) throws TransactionFailureException {
    assert !prepared : "Transaction has already been prepared";
    integrityValidator.validateTransactionStartKnowledge(lastCommittedTxWhenTransactionStarted);
    int noOfCommands = recordChangeSet.changeSize();
    var labelTokenChanges = recordChangeSet.getLabelTokenChanges().changes();
    memoryTracker.allocateHeap(labelTokenChanges.size() * Command.LabelTokenCommand.HEAP_SIZE);
    for (RecordProxy<LabelTokenRecord, Void> record : labelTokenChanges) {
        commands.add(new Command.LabelTokenCommand(commandSerialization, record.getBefore(), record.forReadingLinkage()));
    }
    var relationshipTypeTokenChanges = recordChangeSet.getRelationshipTypeTokenChanges().changes();
    memoryTracker.allocateHeap(relationshipTypeTokenChanges.size() * Command.RelationshipTypeTokenCommand.HEAP_SIZE);
    for (RecordProxy<RelationshipTypeTokenRecord, Void> record : relationshipTypeTokenChanges) {
        commands.add(new Command.RelationshipTypeTokenCommand(commandSerialization, record.getBefore(), record.forReadingLinkage()));
    }
    var propertyKeyTokenChanges = recordChangeSet.getPropertyKeyTokenChanges().changes();
    memoryTracker.allocateHeap(propertyKeyTokenChanges.size() * Command.PropertyKeyTokenCommand.HEAP_SIZE);
    for (RecordProxy<PropertyKeyTokenRecord, Void> record : propertyKeyTokenChanges) {
        commands.add(new Command.PropertyKeyTokenCommand(commandSerialization, record.getBefore(), record.forReadingLinkage()));
    }
    // Collect nodes, relationships, properties
    Command[] nodeCommands = EMPTY_COMMANDS;
    int skippedCommands = 0;
    var nodeChanges = recordChangeSet.getNodeRecords().changes();
    if (!nodeChanges.isEmpty()) {
        memoryTracker.allocateHeap(nodeChanges.size() * Command.NodeCommand.HEAP_SIZE);
        nodeCommands = new Command[nodeChanges.size()];
        int i = 0;
        for (RecordProxy<NodeRecord, Void> change : nodeChanges) {
            NodeRecord record = prepared(change, nodeStore);
            IntegrityValidator.validateNodeRecord(record);
            nodeCommands[i++] = new Command.NodeCommand(commandSerialization, change.getBefore(), record);
        }
        Arrays.sort(nodeCommands, COMMAND_COMPARATOR);
    }
    Command[] relCommands = EMPTY_COMMANDS;
    var relationshipChanges = recordChangeSet.getRelRecords().changes();
    if (!relationshipChanges.isEmpty()) {
        memoryTracker.allocateHeap(relationshipChanges.size() * Command.RelationshipCommand.HEAP_SIZE);
        relCommands = new Command[relationshipChanges.size()];
        int i = 0;
        for (RecordProxy<RelationshipRecord, Void> change : relationshipChanges) {
            relCommands[i++] = new Command.RelationshipCommand(commandSerialization, change.getBefore(), prepared(change, relationshipStore));
        }
        Arrays.sort(relCommands, COMMAND_COMPARATOR);
    }
    Command[] propCommands = EMPTY_COMMANDS;
    var propertyChanges = recordChangeSet.getPropertyRecords().changes();
    if (!propertyChanges.isEmpty()) {
        memoryTracker.allocateHeap(propertyChanges.size() * Command.PropertyCommand.HEAP_SIZE);
        propCommands = new Command[propertyChanges.size()];
        int i = 0;
        for (RecordProxy<PropertyRecord, PrimitiveRecord> change : propertyChanges) {
            propCommands[i++] = new Command.PropertyCommand(commandSerialization, change.getBefore(), prepared(change, propertyStore));
        }
        Arrays.sort(propCommands, COMMAND_COMPARATOR);
    }
    Command[] relGroupCommands = EMPTY_COMMANDS;
    var relationshipGroupChanges = recordChangeSet.getRelGroupRecords().changes();
    if (!relationshipGroupChanges.isEmpty()) {
        memoryTracker.allocateHeap(relationshipGroupChanges.size() * Command.RelationshipGroupCommand.HEAP_SIZE);
        relGroupCommands = new Command[relationshipGroupChanges.size()];
        int i = 0;
        for (RecordProxy<RelationshipGroupRecord, Integer> change : relationshipGroupChanges) {
            if (change.isCreated() && !change.forReadingLinkage().inUse()) {
                /*
                     * This is an edge case that may come up and which we must handle properly. Relationship groups are
                     * not managed by the tx state, since they are created as side effects rather than through
                     * direct calls. However, they differ from say, dynamic records, in that their management can happen
                     * through separate code paths. What we are interested in here is the following scenario.
                     * 0. A node has one less relationship that is required to transition to dense node. The relationships
                     *    it has belong to at least two different types
                     * 1. In the same tx, a relationship is added making the node dense and all the relationships of a type
                     *    are removed from that node. Regardless of the order these operations happen, the creation of the
                     *    relationship (and the transition of the node to dense) will happen first.
                     * 2. A relationship group will be created because of the transition to dense and then deleted because
                     *    all the relationships it would hold are no longer there. This results in a relationship group
                     *    command that appears in the tx as not in use. Depending on the final order of operations, this
                     *    can end up using an id that is higher than the highest id seen so far. This may not be a problem
                     *    for a single instance, but it can result in errors in cases where transactions are applied
                     *    externally, such as backup.
                     *
                     * The way we deal with this issue here is by not issuing a command for that offending record. This is
                     * safe, since the record is not in use and never was, so the high id is not necessary to change and
                     * the store remains consistent.
                     */
                skippedCommands++;
                continue;
            }
            relGroupCommands[i++] = new Command.RelationshipGroupCommand(commandSerialization, change.getBefore(), prepared(change, relationshipGroupStore));
        }
        relGroupCommands = i < relGroupCommands.length ? Arrays.copyOf(relGroupCommands, i) : relGroupCommands;
        Arrays.sort(relGroupCommands, COMMAND_COMPARATOR);
    }
    addFiltered(commands, Mode.CREATE, propCommands, relCommands, relGroupCommands, nodeCommands);
    addFiltered(commands, Mode.UPDATE, propCommands, relCommands, relGroupCommands, nodeCommands);
    addFiltered(commands, Mode.DELETE, relCommands, relGroupCommands, nodeCommands);
    EnumMap<Mode, List<Command>> schemaChangeByMode = new EnumMap<>(Mode.class);
    var schemaRuleChange = recordChangeSet.getSchemaRuleChanges().changes();
    memoryTracker.allocateHeap(schemaRuleChange.size() * Command.SchemaRuleCommand.HEAP_SIZE);
    for (RecordProxy<SchemaRecord, SchemaRule> change : schemaRuleChange) {
        SchemaRecord schemaRecord = change.forReadingLinkage();
        SchemaRule rule = change.getAdditionalData();
        if (schemaRecord.inUse()) {
            integrityValidator.validateSchemaRule(rule);
        }
        Command.SchemaRuleCommand cmd = new Command.SchemaRuleCommand(commandSerialization, change.getBefore(), change.forChangingData(), rule);
        schemaChangeByMode.computeIfAbsent(cmd.getMode(), MODE_TO_ARRAY_LIST).add(cmd);
    }
    commands.addAll(schemaChangeByMode.getOrDefault(Mode.DELETE, Collections.emptyList()));
    commands.addAll(schemaChangeByMode.getOrDefault(Mode.CREATE, Collections.emptyList()));
    commands.addAll(schemaChangeByMode.getOrDefault(Mode.UPDATE, Collections.emptyList()));
    // Add deleted property commands last, so they happen after the schema record changes.
    // This extends the lifetime of property records just past the last moment of use,
    // and prevents reading and deleting of schema records from racing, and making the
    // schema records look malformed.
    addFiltered(commands, Mode.DELETE, propCommands);
    assert commands.size() == noOfCommands - skippedCommands : format("Expected %d final commands, got %d " + "instead, with %d skipped", noOfCommands, commands.size(), skippedCommands);
    if (groupDegreesUpdater.degrees != null) {
        memoryTracker.allocateHeap(groupDegreesUpdater.degrees.size() * Command.GroupDegreeCommand.SHALLOW_SIZE);
        groupDegreesUpdater.degrees.forEachKeyValue((key, delta) -> {
            if (delta.longValue() != 0) {
                long groupId = Command.GroupDegreeCommand.groupIdFromCombinedKey(key);
                RelationshipDirection direction = Command.GroupDegreeCommand.directionFromCombinedKey(key);
                commands.add(new Command.GroupDegreeCommand(groupId, direction, delta.longValue()));
            }
        });
    }
    prepared = true;
}
Also used : RelationshipDirection(org.neo4j.storageengine.api.RelationshipDirection) RelationshipTypeTokenRecord(org.neo4j.kernel.impl.store.record.RelationshipTypeTokenRecord) RelationshipRecord(org.neo4j.kernel.impl.store.record.RelationshipRecord) SchemaRule(org.neo4j.internal.schema.SchemaRule) PropertyKeyTokenRecord(org.neo4j.kernel.impl.store.record.PropertyKeyTokenRecord) NodeRecord(org.neo4j.kernel.impl.store.record.NodeRecord) PropertyRecord(org.neo4j.kernel.impl.store.record.PropertyRecord) SchemaRecord(org.neo4j.kernel.impl.store.record.SchemaRecord) List(java.util.List) ArrayList(java.util.ArrayList) EnumMap(java.util.EnumMap) LabelTokenRecord(org.neo4j.kernel.impl.store.record.LabelTokenRecord) PrimitiveRecord(org.neo4j.kernel.impl.store.record.PrimitiveRecord) RelationshipGroupRecord(org.neo4j.kernel.impl.store.record.RelationshipGroupRecord) Mode(org.neo4j.internal.recordstorage.Command.Mode) StorageCommand(org.neo4j.storageengine.api.StorageCommand)

Example 4 with RelationshipDirection

use of org.neo4j.storageengine.api.RelationshipDirection in project neo4j by neo4j.

the class RelationshipModifierTest method generateRelationshipData.

private List<RelationshipData> generateRelationshipData(int count, long node, IntSupplier typeStrategy, LongSupplier otherNodeStrategy, Supplier<RelationshipDirection> directionStrategy) {
    List<RelationshipData> relationships = new ArrayList<>(count);
    for (int i = 0; i < count; i++) {
        long otherNode = otherNodeStrategy.getAsLong();
        RelationshipDirection direction = directionStrategy.get();
        long start = direction == OUTGOING || direction == LOOP ? node : otherNode;
        long end = direction == INCOMING || direction == LOOP ? node : otherNode;
        relationships.add(new RelationshipData(nextRelationshipId(), typeStrategy.getAsInt(), start, end));
    }
    return relationships;
}
Also used : RelationshipData(org.neo4j.internal.recordstorage.FlatRelationshipModifications.RelationshipData) RelationshipDirection(org.neo4j.storageengine.api.RelationshipDirection) ArrayList(java.util.ArrayList)

Example 5 with RelationshipDirection

use of org.neo4j.storageengine.api.RelationshipDirection in project neo4j by neo4j.

the class RelationshipModifierTest method shouldCreateAndDelete.

// ... other known cases ...
@RepeatedTest(20)
void shouldCreateAndDelete() {
    // given and initial state
    long node = createEmptyNode();
    IntSupplier typeStrategy = randomTypes(10);
    Supplier<RelationshipDirection> directionStrategy = RANDOM_DIRECTION;
    LongSupplier otherNodeStrategy = this::createEmptyNode;
    int maxRelationships = 100;
    List<RelationshipData> expectedRelationships = generateRelationshipData(random.nextInt(0, maxRelationships), node, typeStrategy, otherNodeStrategy, directionStrategy);
    createRelationships(expectedRelationships);
    // ... a set of relationships to create and delete
    List<RelationshipData> relationshipsToCreate = generateRelationshipData((int) random.among(new long[] { 0, 1, 10, 100 }), node, typeStrategy, otherNodeStrategy, directionStrategy);
    int numRelationshipsToDelete = min((int) random.among(new long[] { 0, 1, 10, maxRelationships }), expectedRelationships.size());
    RelationshipData[] relationshipsToDelete = random.selection(expectedRelationships.toArray(RelationshipData[]::new), numRelationshipsToDelete, numRelationshipsToDelete, false);
    // ... and rules for how the world changes "concurrently" while we perform these modifications
    // on locked
    monitors.addMonitorListener(new ResourceTypeLockOrderVerifier());
    // on read
    monitors.addMonitorListener(new ChangeWorldOnReadMonitor(node, typeStrategy, otherNodeStrategy, directionStrategy, expectedRelationships));
    // when
    RelationshipModifications modifications = modifications(relationshipsToCreate.toArray(RelationshipData[]::new), relationshipsToDelete);
    modify(modifications);
    applyModificationsToExpectedRelationships(modifications, expectedRelationships);
    // then
    assertThat(readRelationshipsFromStore(node, store)).isEqualTo(asSet(expectedRelationships));
}
Also used : RelationshipData(org.neo4j.internal.recordstorage.FlatRelationshipModifications.RelationshipData) RelationshipModifications(org.neo4j.storageengine.api.txstate.RelationshipModifications) RelationshipDirection(org.neo4j.storageengine.api.RelationshipDirection) IntSupplier(java.util.function.IntSupplier) LongSupplier(java.util.function.LongSupplier) RepeatedTest(org.junit.jupiter.api.RepeatedTest)

Aggregations

RelationshipDirection (org.neo4j.storageengine.api.RelationshipDirection)5 ArrayList (java.util.ArrayList)2 Test (org.junit.jupiter.api.Test)2 RelationshipData (org.neo4j.internal.recordstorage.FlatRelationshipModifications.RelationshipData)2 RelationshipChangesForNode.createRelationshipChangesForNode (org.neo4j.kernel.impl.api.state.RelationshipChangesForNode.createRelationshipChangesForNode)2 RelationshipModifications (org.neo4j.storageengine.api.txstate.RelationshipModifications)2 EnumMap (java.util.EnumMap)1 HashMap (java.util.HashMap)1 List (java.util.List)1 Map (java.util.Map)1 Function (java.util.function.Function)1 IntSupplier (java.util.function.IntSupplier)1 LongSupplier (java.util.function.LongSupplier)1 Assertions.assertThat (org.assertj.core.api.Assertions.assertThat)1 LongIterator (org.eclipse.collections.api.iterator.LongIterator)1 MutableIntObjectMap (org.eclipse.collections.api.map.primitive.MutableIntObjectMap)1 MutableLongSet (org.eclipse.collections.api.set.primitive.MutableLongSet)1 IntObjectMaps (org.eclipse.collections.impl.factory.primitive.IntObjectMaps)1 LongSets (org.eclipse.collections.impl.factory.primitive.LongSets)1 RepeatedTest (org.junit.jupiter.api.RepeatedTest)1