Skip to content

Commit 896a064

Browse files
Mats-SXFlorentinD
andcommitted
Allow GraphStore and Graph to have zero relationships
Co-authored-by: Florentin Dörre <florentin.dorre@neotechnology.com>
1 parent 0b23b57 commit 896a064

File tree

4 files changed

+84
-39
lines changed

4 files changed

+84
-39
lines changed

core/src/main/java/org/neo4j/gds/core/loading/CSRGraphStore.java

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,10 @@
4949
import org.neo4j.gds.api.schema.RelationshipPropertySchema;
5050
import org.neo4j.gds.api.schema.RelationshipSchema;
5151
import org.neo4j.gds.core.Aggregation;
52-
import org.neo4j.gds.core.ProcedureConstants;
5352
import org.neo4j.gds.core.huge.CSRCompositeRelationshipIterator;
5453
import org.neo4j.gds.core.huge.HugeGraph;
5554
import org.neo4j.gds.core.huge.NodeFilteredGraph;
5655
import org.neo4j.gds.core.huge.UnionGraph;
57-
import org.neo4j.gds.core.loading.construction.GraphFactory;
5856
import org.neo4j.gds.core.utils.TimeUtil;
5957
import org.neo4j.gds.utils.ExceptionUtil;
6058
import org.neo4j.gds.utils.StringJoining;
@@ -115,18 +113,13 @@ public static CSRGraphStore of(
115113
Map<RelationshipType, RelationshipPropertyStore> relationshipPropertyStores,
116114
int concurrency
117115
) {
118-
// A graph store must contain at least one topology, even if it is empty.
119-
var topologies = relationships.isEmpty()
120-
? Map.of(RelationshipType.ALL_RELATIONSHIPS, GraphFactory.emptyRelationships(nodes).topology())
121-
: relationships;
122-
123116
return new CSRGraphStore(
124117
databaseId,
125118
capabilities,
126119
schema,
127120
nodes,
128121
nodePropertyStore == null ? NodePropertyStore.empty() : nodePropertyStore,
129-
topologies,
122+
relationships,
130123
relationshipPropertyStores,
131124
concurrency
132125
);
@@ -478,27 +471,8 @@ public DeletionResult deleteRelationships(RelationshipType relationshipType) {
478471
}
479472

480473
@Override
481-
public Graph getGraph(Collection<NodeLabel> nodeLabels) {
482-
var filteredNodes = getFilteredIdMap(nodeLabels);
483-
var filteredNodeProperties = filterNodeProperties(nodeLabels);
484-
485-
var graphSchema = GraphSchema.of(
486-
schema().nodeSchema(),
487-
RelationshipSchema.empty(),
488-
schema.graphProperties()
489-
);
490-
491-
var initialGraph = HugeGraph.create(
492-
nodes,
493-
graphSchema,
494-
filteredNodeProperties,
495-
Relationships.Topology.EMPTY,
496-
Optional.empty()
497-
);
498-
499-
return filteredNodes.isPresent()
500-
? new NodeFilteredGraph(initialGraph, filteredNodes.get())
501-
: initialGraph;
474+
public CSRGraph getGraph(Collection<NodeLabel> nodeLabels) {
475+
return getGraph(nodeLabels, List.of(), Optional.empty());
502476
}
503477

504478
@Override
@@ -508,11 +482,18 @@ public CSRGraph getGraph(
508482
Optional<String> maybeRelationshipProperty
509483
) {
510484
validateInput(relationshipTypes, maybeRelationshipProperty);
511-
return createGraph(nodeLabels, relationshipTypes, maybeRelationshipProperty);
485+
if (relationshipTypes.isEmpty()) {
486+
return createNodeOnlyGraph(nodeLabels);
487+
} else {
488+
return createGraph(nodeLabels, relationshipTypes, maybeRelationshipProperty);
489+
}
512490
}
513491

514492
@Override
515493
public CSRGraph getUnion() {
494+
if (relationships.isEmpty()) {
495+
return getGraph(nodeLabels());
496+
}
516497
var graphs = relationships
517498
.keySet()
518499
.stream()
@@ -681,6 +662,29 @@ private CSRGraph createGraph(
681662
return UnionGraph.of(filteredGraphs);
682663
}
683664

665+
private CSRGraph createNodeOnlyGraph(Collection<NodeLabel> nodeLabels) {
666+
var filteredNodes = getFilteredIdMap(nodeLabels);
667+
var filteredNodeProperties = filterNodeProperties(nodeLabels);
668+
669+
var graphSchema = GraphSchema.of(
670+
schema().nodeSchema(),
671+
RelationshipSchema.empty(),
672+
schema.graphProperties()
673+
);
674+
675+
var initialGraph = HugeGraph.create(
676+
nodes,
677+
graphSchema,
678+
filteredNodeProperties,
679+
Relationships.Topology.EMPTY,
680+
Optional.empty()
681+
);
682+
683+
return filteredNodes.isPresent()
684+
? new NodeFilteredGraph(initialGraph, filteredNodes.get())
685+
: initialGraph;
686+
}
687+
684688
@NotNull
685689
private Optional<IdMap> getFilteredIdMap(Collection<NodeLabel> filteredLabels) {
686690
boolean loadAllNodes = filteredLabels.containsAll(nodeLabels());
@@ -744,13 +748,6 @@ private void validateInput(
744748
Collection<RelationshipType> relationshipTypes,
745749
Optional<String> maybeRelationshipProperty
746750
) {
747-
if (relationshipTypes.isEmpty()) {
748-
throw new IllegalArgumentException(formatWithLocale(
749-
"The parameter '%s' should not be empty. Use '*' to load all relationship types.",
750-
ProcedureConstants.RELATIONSHIP_TYPES
751-
));
752-
}
753-
754751
relationshipTypes.forEach(relationshipType -> {
755752
if (!relationships.containsKey(relationshipType)) {
756753
throw new IllegalArgumentException(formatWithLocale(

core/src/test/java/org/neo4j/gds/core/loading/CSRGraphStoreTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,17 @@
2020
package org.neo4j.gds.core.loading;
2121

2222
import org.junit.jupiter.api.Test;
23+
import org.neo4j.gds.NodeLabel;
2324
import org.neo4j.gds.RelationshipType;
2425
import org.neo4j.gds.gdl.GdlFactory;
2526

27+
import java.util.List;
28+
import java.util.Optional;
29+
2630
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
32+
import static org.neo4j.gds.TestSupport.assertGraphEquals;
33+
import static org.neo4j.gds.TestSupport.fromGdl;
2734

2835
class CSRGraphStoreTest {
2936

@@ -43,4 +50,36 @@ void deleteAdditionalRelationshipTypes() {
4350
assertThat(del2.deletedProperties()).isEmpty();
4451
}
4552

53+
@Test
54+
void validateRelationshipTypesWhenNoneExist() {
55+
GdlFactory factory = GdlFactory.of("(a), (b)");
56+
var graphStore = factory.build();
57+
58+
assertThatThrownBy(() -> graphStore.getGraph(
59+
List.of(NodeLabel.ALL_NODES),
60+
List.of(RelationshipType.of("X")),
61+
Optional.empty()
62+
)).hasMessageContaining("No relationships have been loaded for relationship type").hasMessageContaining("X");
63+
}
64+
65+
@Test
66+
void gettingGraphsWithRelationshipTypes() {
67+
GdlFactory factory = GdlFactory.of("()-[:T]->()-[:R]->()-[:R]->()");
68+
var graphStore = factory.build();
69+
70+
var t_graph = graphStore.getGraph(graphStore.nodeLabels(), List.of(RelationshipType.of("T")), Optional.empty());
71+
var r_graph = graphStore.getGraph(graphStore.nodeLabels(), List.of(RelationshipType.of("R")), Optional.empty());
72+
var t_r_graph = graphStore.getGraph(
73+
graphStore.nodeLabels(),
74+
List.of(RelationshipType.of("R"), RelationshipType.of("T")),
75+
Optional.empty()
76+
);
77+
var none_graph = graphStore.getGraph(graphStore.nodeLabels(), List.of(), Optional.empty());
78+
79+
assertGraphEquals(fromGdl("()-[:T]->(), (), ()"), t_graph);
80+
assertGraphEquals(fromGdl("(), ()-[:R]->()-[:R]->()"), r_graph);
81+
assertGraphEquals(fromGdl("()-[:T]->()-[:R]->()-[:R]->()"), t_r_graph);
82+
assertGraphEquals(fromGdl("(), (), (), ()"), none_graph);
83+
}
84+
4685
}

proc/community/src/test/java/org/neo4j/gds/wcc/WccStatsProcTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ public WccStatsConfig createConfig(CypherMapWrapper mapWrapper) {
5050
return WccStatsConfig.of(mapWrapper);
5151
}
5252

53+
@Test
54+
void testRelationshipTypesEmpty() {
55+
runQuery("CALL gds.graph.project('g', '*', '*')");
56+
57+
assertCypherResult("CALL gds.wcc.stats('g', {relationshipTypes: []}) YIELD componentCount", List.of(Map.of(
58+
"componentCount", 10L
59+
)));
60+
}
61+
5362
@Test
5463
void yields() {
5564
loadGraph(DEFAULT_GRAPH_NAME);

test-utils/src/test/java/org/neo4j/gds/extension/TestGraphTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ void usesIdFunctionForOriginalId() {
6969
CSRGraph bGraph = GdlFactory
7070
.of("(:A), (b:B), (:B)")
7171
.build()
72-
.getGraph(List.of(NodeLabel.of("B")), List.of(RelationshipType.ALL_RELATIONSHIPS), Optional.empty());
72+
.getGraph(List.of(NodeLabel.of("B")), List.of(), Optional.empty());
7373
TestGraph g = new TestGraph(bGraph, (a) -> a.equals("b") ? 1 : 2, "foo");
7474
assertEquals(1, g.toOriginalNodeId("b"));
7575
assertEquals(2, g.toOriginalNodeId("notB"));
@@ -80,7 +80,7 @@ void usesInnerGraphForMappedId() {
8080
CSRGraph bGraph = GdlFactory
8181
.of("(:A), (b:B), (:B)")
8282
.build()
83-
.getGraph(List.of(NodeLabel.of("B")), List.of(RelationshipType.ALL_RELATIONSHIPS), Optional.empty());
83+
.getGraph(List.of(NodeLabel.of("B")), List.of(), Optional.empty());
8484
TestGraph g = new TestGraph(bGraph, (a) -> a.equals("b") ? 1 : 2, "foo");
8585
assertEquals(0, g.toMappedNodeId("b"));
8686
assertEquals(1, g.toMappedNodeId("notB"));

0 commit comments

Comments
 (0)