Skip to content

Commit

Permalink
Add tx-state awareness to RelationshipScanCursor
Browse files Browse the repository at this point in the history
This implementation duplicates code from NodeCursor, but this logic was
hard to share in a non-complicated way because of the requirement to
inherit their respective *Record class.
  • Loading branch information
fickludd committed Jan 23, 2018
1 parent 864c86e commit 6f8f4f9
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 37 deletions.
Expand Up @@ -75,10 +75,10 @@ void nodeIndexSeek( IndexReference index, NodeValueIndexCursor cursor, IndexOrde


/** /**
* Checks if a node exists in the database * Checks if a node exists in the database
* @param id The id of the node to check * @param reference The reference of the node to check
* @return <tt>true</tt> if the node exists, otherwise <tt>false</tt> * @return <tt>true</tt> if the node exists, otherwise <tt>false</tt>
*/ */
boolean nodeExists( long id ); boolean nodeExists( long reference );


/** /**
* @param reference * @param reference
Expand Down
Expand Up @@ -37,7 +37,7 @@
import static org.neo4j.values.storable.Values.stringValue; import static org.neo4j.values.storable.Values.stringValue;


@SuppressWarnings( "Duplicates" ) @SuppressWarnings( "Duplicates" )
public abstract class TransactionStateTestBase<G extends KernelAPIWriteTestSupport> extends KernelAPIWriteTestBase<G> public abstract class NodeTransactionStateTestBase<G extends KernelAPIWriteTestSupport> extends KernelAPIWriteTestBase<G>
{ {
@Test @Test
public void shouldSeeNodeInTransaction() throws Exception public void shouldSeeNodeInTransaction() throws Exception
Expand Down
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2002-2018 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.internal.kernel.api;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

@SuppressWarnings( "Duplicates" )
public abstract class RelationshipTransactionStateTestBase<G extends KernelAPIWriteTestSupport> extends KernelAPIWriteTestBase<G>
{
@Test
public void shouldSeeSingleRelationshipInTransaction() throws Exception
{
int label;
long n1, n2;
try ( Transaction tx = session.beginTransaction() )
{
n1 = tx.dataWrite().nodeCreate();
n2 = tx.dataWrite().nodeCreate();
long decoyNode = tx.dataWrite().nodeCreate();
label = tx.tokenWrite().relationshipTypeGetOrCreateForName( "R" ); // to have >1 relationship in the db
tx.dataWrite().relationshipCreate( n2, label, decoyNode );
tx.success();
}

try ( Transaction tx = session.beginTransaction() )
{
label = tx.tokenWrite().relationshipTypeGetOrCreateForName( "R" );
long r = tx.dataWrite().relationshipCreate( n1, label, n2 );
try ( RelationshipScanCursor relationship = cursors.allocateRelationshipScanCursor() )
{
tx.dataRead().singleRelationship( r, relationship );
assertTrue( "should find relationship", relationship.next() );

assertEquals( label, relationship.label() );
assertEquals( n1, relationship.sourceNodeReference() );
assertEquals( n2, relationship.targetNodeReference() );
assertEquals( r, relationship.relationshipReference() );

assertFalse( "should only find one relationship", relationship.next() );
}
tx.success();
}
}
}
Expand Up @@ -95,23 +95,23 @@ public AllStoreHolder( StorageEngine engine,
} }


@Override @Override
public boolean nodeExists( long id ) public boolean nodeExists( long reference )
{ {
ktx.assertOpen(); ktx.assertOpen();


if ( hasTxStateWithChanges() ) if ( hasTxStateWithChanges() )
{ {
TransactionState txState = txState(); TransactionState txState = txState();
if ( txState.nodeIsDeletedInThisTx( id ) ) if ( txState.nodeIsDeletedInThisTx( reference ) )
{ {
return false; return false;
} }
else if ( txState.nodeIsAddedInThisTx( id ) ) else if ( txState.nodeIsAddedInThisTx( reference ) )
{ {
return true; return true;
} }
} }
return storeReadLayer.nodeExists( id ); return storeReadLayer.nodeExists( reference );
} }


@Override @Override
Expand Down
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2002-2018 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.newapi;

enum HasChanges
{
MAYBE,
YES,
NO
}
Expand Up @@ -48,13 +48,6 @@ class NodeCursor extends NodeRecord implements org.neo4j.internal.kernel.api.Nod
private HasChanges hasChanges = HasChanges.MAYBE; private HasChanges hasChanges = HasChanges.MAYBE;
private Set<Long> addedNodes; private Set<Long> addedNodes;


private enum HasChanges
{
MAYBE,
YES,
NO
}

NodeCursor() NodeCursor()
{ {
super( NO_ID ); super( NO_ID );
Expand Down Expand Up @@ -203,6 +196,7 @@ else if ( hasChanges && txs.nodeIsDeletedInThisTx( next ) )
{ {
read.node( this, next++, pageCursor ); read.node( this, next++, pageCursor );
} }

if ( next > highMark ) if ( next > highMark )
{ {
if ( isSingle() ) if ( isSingle() )
Expand Down
Expand Up @@ -19,18 +19,32 @@
*/ */
package org.neo4j.kernel.impl.newapi; package org.neo4j.kernel.impl.newapi;


import java.util.Set;

import org.neo4j.internal.kernel.api.RelationshipDataAccessor; import org.neo4j.internal.kernel.api.RelationshipDataAccessor;
import org.neo4j.kernel.impl.api.RelationshipVisitor;
import org.neo4j.kernel.impl.store.record.RelationshipRecord; import org.neo4j.kernel.impl.store.record.RelationshipRecord;


abstract class RelationshipCursor extends RelationshipRecord implements RelationshipDataAccessor import static java.util.Collections.emptySet;

abstract class RelationshipCursor extends RelationshipRecord implements RelationshipDataAccessor, RelationshipVisitor<RuntimeException>
{ {
Read read; Read read;
private HasChanges hasChanges = HasChanges.MAYBE;
Set<Long> addedRelationships;


RelationshipCursor() RelationshipCursor()
{ {
super( NO_ID ); super( NO_ID );
} }


protected void init( Read read )
{
this.read = read;
this.hasChanges = HasChanges.MAYBE;
this.addedRelationships = emptySet();
}

@Override @Override
public long relationshipReference() public long relationshipReference()
{ {
Expand Down Expand Up @@ -84,4 +98,52 @@ public long propertiesReference()
{ {
return getNextProp(); return getNextProp();
} }

protected abstract boolean shouldGetAddedTxStateSnapshot();

/**
* RelationshipCursor should only see changes that are there from the beginning
* otherwise it will not be stable.
*/
protected boolean hasChanges()
{
switch ( hasChanges )
{
case MAYBE:
boolean changes = read.hasTxStateWithChanges();
if ( changes )
{
if ( shouldGetAddedTxStateSnapshot() )
{
addedRelationships = read.txState().addedAndRemovedRelationships().getAddedSnapshot();
}
hasChanges = HasChanges.YES;
}
else
{
hasChanges = HasChanges.NO;
}
return changes;
case YES:
return true;
case NO:
return false;
default:
throw new IllegalStateException( "Style guide, why are you making me do this" );
}
}

protected void loadFromTxState( long reference )
{
read.txState().relationshipVisit( reference, this );
}

@Override
public void visit( long relationshipId, int typeId, long startNodeId, long endNodeId ) throws RuntimeException
{
setId( relationshipId );
setType( typeId );
setFirstNode( startNodeId );
setSecondNode( endNodeId );
}
} }
Expand Up @@ -20,6 +20,7 @@
package org.neo4j.kernel.impl.newapi; package org.neo4j.kernel.impl.newapi;


import org.neo4j.io.pagecache.PageCursor; import org.neo4j.io.pagecache.PageCursor;
import org.neo4j.kernel.api.txstate.TransactionState;


class RelationshipScanCursor extends RelationshipCursor implements org.neo4j.internal.kernel.api.RelationshipScanCursor class RelationshipScanCursor extends RelationshipCursor implements org.neo4j.internal.kernel.api.RelationshipScanCursor
{ {
Expand All @@ -41,7 +42,7 @@ void scan( int label, Read read )
next = 0; next = 0;
this.label = label; this.label = label;
highMark = read.relationshipHighMark(); highMark = read.relationshipHighMark();
this.read = read; init( read );
} }


void single( long reference, Read read ) void single( long reference, Read read )
Expand All @@ -57,46 +58,72 @@ void single( long reference, Read read )
next = reference; next = reference;
label = -1; label = -1;
highMark = NO_ID; highMark = NO_ID;
this.read = read; init( read );
} }


@Override @Override
public boolean next() public boolean next()
{ {
if ( next == NO_ID )
{
reset();
return false;
}

// Check tx state
boolean hasChanges = hasChanges();
TransactionState txs = hasChanges ? read.txState() : null;

do do
{ {
if ( next == NO_ID ) if ( hasChanges && containsRelationship( txs ) )
{
loadFromTxState( next++ );
setInUse( true );
}
else if ( hasChanges && txs.relationshipIsDeletedInThisTx( next ) )
{ {
reset(); next++;
return false; setInUse( false );
} }
do else
{ {
read.relationship( this, next++, pageCursor ); read.relationship( this, next++, pageCursor );
if ( next > highMark ) }

if ( next > highMark )
{
if ( isSingle() )
{ {
if ( highMark == NO_ID ) next = NO_ID;
return isWantedLabelAndInUse();
}
else
{
highMark = read.relationshipHighMark();
if ( next > highMark )
{ {
next = NO_ID; next = NO_ID;
return (label == -1 || label() == label) && inUse(); return isWantedLabelAndInUse();
}
else
{
highMark = read.relationshipHighMark();
if ( next > highMark )
{
next = NO_ID;
return (label == -1 || label() == label) && inUse();
}
} }
} }
} }
while ( !inUse() );
} }
while ( label != -1 && label() != label ); while ( !isWantedLabelAndInUse() );

return true; return true;
} }


private boolean isWantedLabelAndInUse()
{
return (label == -1 || label() == label) && inUse();
}

private boolean containsRelationship( TransactionState txs )
{
return isSingle() ? txs.relationshipIsAddedInThisTx( next ) : addedRelationships.contains( next );
}

@Override @Override
public boolean shouldRetry() public boolean shouldRetry()
{ {
Expand Down Expand Up @@ -139,4 +166,14 @@ public String toString()
", underlying record=" + super.toString() + " ]"; ", underlying record=" + super.toString() + " ]";
} }
} }

private boolean isSingle()
{
return highMark == NO_ID;
}

protected boolean shouldGetAddedTxStateSnapshot()
{
return !isSingle();
}
} }
Expand Up @@ -438,6 +438,12 @@ private void reset()
buffer = null; buffer = null;
} }


@Override
protected boolean shouldGetAddedTxStateSnapshot()
{
return true;
}

@Override @Override
public boolean isClosed() public boolean isClosed()
{ {
Expand Down

0 comments on commit 6f8f4f9

Please sign in to comment.