Skip to content

Commit 7d6eed4

Browse files
Implement relationship range scan
1 parent d8ee182 commit 7d6eed4

File tree

5 files changed

+102
-12
lines changed

5 files changed

+102
-12
lines changed

cypher/api/storage-engine-adapter/src/main/java/org/neo4j/gds/compat/AbstractInMemoryRelationshipScanCursor.java

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@
2121

2222
import org.neo4j.gds.core.cypher.CypherGraphStore;
2323
import org.neo4j.gds.storageengine.InMemoryRelationshipCursor;
24+
import org.neo4j.internal.recordstorage.InMemoryRelationshipScan;
2425
import org.neo4j.storageengine.api.AllRelationshipsScan;
2526
import org.neo4j.storageengine.api.RelationshipSelection;
2627
import org.neo4j.storageengine.api.StorageRelationshipScanCursor;
2728
import org.neo4j.token.TokenHolders;
2829

30+
import static java.lang.Math.min;
31+
2932
public abstract class AbstractInMemoryRelationshipScanCursor extends InMemoryRelationshipCursor implements StorageRelationshipScanCursor {
3033

34+
private long highMark;
35+
3136
public AbstractInMemoryRelationshipScanCursor(CypherGraphStore graphStore, TokenHolders tokenHolders) {
3237
super(graphStore, tokenHolders);
3338
}
@@ -37,30 +42,23 @@ public void scan() {
3742
reset();
3843
this.sourceId = 0;
3944
this.selection = RelationshipSelection.ALL_RELATIONSHIPS;
45+
this.highMark = maxRelationshipId;
4046
}
4147

4248
@Override
4349
public void single(long reference) {
4450
reset();
4551
setId(reference - 1);
52+
this.highMark = reference;
4653
this.selection = RelationshipSelection.ALL_RELATIONSHIPS;
4754

48-
graphStore.relationshipIds().resolveRelationshipId(reference, (nodeId, offset, context) -> {
49-
this.sourceId = nodeId;
50-
findContextAndInitializeCursor(context);
51-
52-
for (long i = 0; i < offset; i++) {
53-
next();
54-
}
55-
56-
return null;
57-
});
55+
initializeForRelationshipReference(reference);
5856
}
5957

6058
@Override
6159
public boolean next() {
6260
if (super.next()) {
63-
return true;
61+
return getId() <= highMark;
6462
} else {
6563
this.sourceId++;
6664
if (this.sourceId >= graphStore.nodeCount()) {
@@ -73,6 +71,33 @@ public boolean next() {
7371
}
7472

7573
public boolean scanBatch(AllRelationshipsScan scan, int sizeHint) {
76-
throw new UnsupportedOperationException();
74+
if (getId() != NO_ID) {
75+
reset();
76+
}
77+
78+
highMark = maxRelationshipId;
79+
return ((InMemoryRelationshipScan) scan).scanBatch(sizeHint, this);
80+
}
81+
82+
public boolean scanRange(long start, long stop) {
83+
reset();
84+
this.selection = RelationshipSelection.ALL_RELATIONSHIPS;
85+
highMark = min(stop, maxRelationshipId);
86+
87+
initializeForRelationshipReference(start);
88+
return true;
89+
}
90+
91+
private void initializeForRelationshipReference(long reference) {
92+
graphStore.relationshipIds().resolveRelationshipId(reference, (nodeId, offset, context) -> {
93+
this.sourceId = nodeId;
94+
findContextAndInitializeCursor(context);
95+
96+
for (long i = 0; i < offset; i++) {
97+
next();
98+
}
99+
setId(reference - 1);
100+
return null;
101+
});
77102
}
78103
}

cypher/api/storage-engine-adapter/src/main/java/org/neo4j/gds/storageengine/InMemoryRelationshipCursor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public abstract class InMemoryRelationshipCursor extends RelationshipRecord impl
4848
private final List<PropertyCursor[]> propertyCursorCache;
4949
private MutableDoubleList propertyValuesCache;
5050

51+
protected long maxRelationshipId;
52+
5153
protected long sourceId;
5254
protected long targetId;
5355
protected RelationshipSelection selection;
@@ -67,6 +69,7 @@ public InMemoryRelationshipCursor(CypherGraphStore graphStore, TokenHolders toke
6769
this.propertyCursorCache = new ArrayList<>();
6870
this.propertyValuesCache = new DoubleArrayList();
6971

72+
this.maxRelationshipId = 0;
7073
this.graphStore.relationshipIds().registerUpdateListener(this::newRelationshipIdContextAdded);
7174
}
7275

@@ -163,6 +166,7 @@ private void newRelationshipIdContextAdded(RelationshipIds.RelationshipIdContext
163166
);
164167
var newSize = this.propertyCursorCache.stream().mapToInt(cursor -> cursor.length).max().orElse(0);
165168
this.propertyValuesCache = new DoubleArrayList(new double[newSize]);
169+
this.maxRelationshipId += relationshipIdContext.relationshipCount();
166170
}
167171

168172
private boolean progressToNextContext() {
@@ -191,9 +195,13 @@ private boolean progressToNextContext() {
191195

192196
protected void findContextAndInitializeCursor(RelationshipIds.RelationshipIdContext context) {
193197
for (int i = 0; i < relationshipIdContexts.size(); i++) {
198+
if (i > 0) {
199+
relationshipTypeOffset += relationshipIdContexts.get(i - 1).relationshipCount();
200+
}
194201
if (relationshipIdContexts.get(i) == context) {
195202
this.relationshipContextIndex = i;
196203
initializeCursorForContext(context);
204+
break;
197205
}
198206
}
199207
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
* This file contains proprietary code that is only available via a commercial license from Neo4j.
5+
* For more information, see https://neo4j.com/contact-us/
6+
*/
7+
package org.neo4j.internal.recordstorage;
8+
9+
import org.neo4j.gds.compat.AbstractInMemoryRelationshipScanCursor;
10+
import org.neo4j.storageengine.api.AllRelationshipsScan;
11+
12+
public class InMemoryRelationshipScan extends BaseRecordScan<AbstractInMemoryRelationshipScanCursor> implements AllRelationshipsScan {
13+
14+
@Override
15+
boolean scanRange(AbstractInMemoryRelationshipScanCursor cursor, long start, long stopInclusive) {
16+
return cursor.scanRange(start, stopInclusive);
17+
}
18+
19+
@Override
20+
public boolean scanBatch(int sizeHint, AbstractInMemoryRelationshipScanCursor cursor) {
21+
return super.scanBatch(sizeHint, cursor);
22+
}
23+
}

cypher/cypher-test/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ description = 'Neo4j Graph Data Science :: Storage Engine Testing'
55
group = 'org.neo4j.gds'
66

77
dependencies {
8+
testAnnotationProcessor project(':annotations')
89
testAnnotationProcessor group: 'org.immutables', name: 'builder', version: ver.'immutables'
910
testAnnotationProcessor group: 'org.immutables', name: 'value', version: ver.'immutables'
1011

cypher/cypher-test/src/test/java/org/neo4j/gds/storageengine/InMemoryRelationshipScanCursorTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.junit.jupiter.api.Test;
2323
import org.neo4j.gds.PropertyMapping;
2424
import org.neo4j.gds.StoreLoaderBuilder;
25+
import org.neo4j.gds.annotation.ValueClass;
2526
import org.neo4j.gds.api.GraphStore;
2627
import org.neo4j.gds.compat.AbstractInMemoryRelationshipScanCursor;
2728
import org.neo4j.gds.compat.InMemoryPropertySelection;
@@ -33,7 +34,9 @@
3334
import org.neo4j.gds.junit.annotation.DisableForNeo4jVersion;
3435
import org.neo4j.values.storable.ValueGroup;
3536

37+
import java.util.HashMap;
3638
import java.util.List;
39+
import java.util.Map;
3740

3841
import static org.assertj.core.api.Assertions.assertThat;
3942

@@ -118,4 +121,34 @@ void shouldSupportPropertiesForSingleRelationship() {
118121
assertThat(propertyCursor.propertyType()).isEqualTo(ValueGroup.NUMBER);
119122
assertThat(propertyCursor.propertyKey()).isEqualTo(relationshipScanCursor.tokenHolders.propertyKeyTokens().getIdByName("relProp"));
120123
}
124+
125+
@Test
126+
void shouldPerformRangeScan() {
127+
relationshipScanCursor.scanRange(1, 3);
128+
129+
var actualRelationships = new HashMap<Long, Relationship>();
130+
while(relationshipScanCursor.next()) {
131+
actualRelationships.put(
132+
relationshipScanCursor.getId(),
133+
ImmutableRelationship.of(
134+
relationshipScanCursor.sourceNodeReference(),
135+
relationshipScanCursor.targetNodeReference()
136+
)
137+
);
138+
}
139+
140+
var expectedRelationships = Map.of(
141+
1L, ImmutableRelationship.of(idFunction.of("a"), idFunction.of("c")),
142+
2L, ImmutableRelationship.of(idFunction.of("b"), idFunction.of("c")),
143+
3L, ImmutableRelationship.of(idFunction.of("a"), idFunction.of("a"))
144+
);
145+
146+
assertThat(actualRelationships).containsExactlyInAnyOrderEntriesOf(expectedRelationships);
147+
}
148+
149+
@ValueClass
150+
interface Relationship {
151+
long source();
152+
long target();
153+
}
121154
}

0 commit comments

Comments
 (0)