Skip to content

Commit 4199dff

Browse files
committed
Support loading inverse relationship properties
1 parent 1788131 commit 4199dff

File tree

3 files changed

+94
-57
lines changed

3 files changed

+94
-57
lines changed

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

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package org.neo4j.gds.core.loading;
2121

22+
import org.immutables.value.Value;
2223
import org.neo4j.gds.PropertyMappings;
2324
import org.neo4j.gds.RelationshipProjection;
2425
import org.neo4j.gds.RelationshipType;
@@ -51,6 +52,12 @@ public interface RelationshipsAndProperties {
5152

5253
Map<RelationshipType, Direction> directions();
5354

55+
@Value.Parameter(false)
56+
@Value.Default
57+
default Map<RelationshipType, SingleTypeRelationshipImportResult> importResults() {
58+
return Map.of();
59+
}
60+
5461
static RelationshipsAndProperties of(Map<RelationshipTypeAndProjection, List<RelationshipsAndDirection>> relationshipsByType) {
5562
var relTypeCount = relationshipsByType.size();
5663
Map<RelationshipType, Relationships.Topology> topologies = new HashMap<>(relTypeCount);
@@ -100,59 +107,46 @@ static RelationshipsAndProperties of(Map<RelationshipTypeAndProjection, List<Rel
100107
* @return a wrapper type ready to be consumed by a {@link org.neo4j.gds.api.GraphStore}
101108
*/
102109
static RelationshipsAndProperties of(Collection<SingleTypeRelationshipImporter.SingleTypeRelationshipImportContext> importContexts) {
103-
var relTypeCount = importContexts.size();
104-
Map<RelationshipType, ImmutableTopology.Builder> relationshipBuilders = new HashMap<>(relTypeCount);
105-
Map<RelationshipType, RelationshipPropertyStore> relationshipPropertyStores = new HashMap<>(relTypeCount);
106-
Map<RelationshipType, Direction> directions = new HashMap<>(relTypeCount);
110+
var builders = new HashMap<RelationshipType, ImmutableSingleTypeRelationshipImportResult.Builder>(importContexts.size());
107111

108112
importContexts.forEach((importContext) -> {
109113
var adjacencyListsWithProperties = importContext.singleTypeRelationshipImporter().build();
114+
var isInverseRelationship = importContext.inverseOfRelationshipType().isPresent();
110115

111-
var adjacency = adjacencyListsWithProperties.adjacency();
112-
var properties = adjacencyListsWithProperties.properties();
113-
var relationshipCount = adjacencyListsWithProperties.relationshipCount();
114-
var relationshipProjection = importContext.relationshipProjection();
116+
var direction = Direction.fromOrientation(importContext.relationshipProjection().orientation());
115117

116-
var topologyBuilder = relationshipBuilders.computeIfAbsent(
117-
importContext.relationshipType(),
118-
relationshipType -> ImmutableTopology.builder()
119-
.elementCount(relationshipCount)
120-
.isMultiGraph(relationshipProjection.isMultiGraph())
121-
);
118+
var topology = ImmutableTopology.builder()
119+
.adjacencyList(adjacencyListsWithProperties.adjacency())
120+
.elementCount(adjacencyListsWithProperties.relationshipCount())
121+
.isMultiGraph(importContext.relationshipProjection().isMultiGraph())
122+
.build();
122123

123-
importContext.inverseOfRelationshipType().ifPresentOrElse(
124-
__ -> topologyBuilder.inverseAdjacencyList(adjacency),
125-
() -> topologyBuilder.adjacencyList(adjacency)
124+
var properties = constructRelationshipPropertyStore(
125+
importContext.relationshipProjection(),
126+
adjacencyListsWithProperties.properties(),
127+
adjacencyListsWithProperties.relationshipCount()
126128
);
127129

128-
// TODO: we only add the properties for the forward relationships for now
129-
if (!relationshipProjection.properties().isEmpty() && importContext.inverseOfRelationshipType().isEmpty()) {
130-
relationshipPropertyStores.put(
131-
importContext.relationshipType(),
132-
constructRelationshipPropertyStore(
133-
relationshipProjection,
134-
properties,
135-
relationshipCount
136-
)
137-
);
138-
}
139-
140-
directions.put(
130+
var importResultBuilder = builders.computeIfAbsent(
141131
importContext.relationshipType(),
142-
Direction.fromOrientation(importContext.relationshipProjection().orientation())
132+
relationshipType -> ImmutableSingleTypeRelationshipImportResult.builder().direction(direction)
143133
);
134+
135+
if (isInverseRelationship) {
136+
importResultBuilder.inverseTopology(topology).inverseProperties(properties);
137+
} else {
138+
importResultBuilder.forwardTopology(topology).forwardProperties(properties);
139+
}
144140
});
145141

146-
var relationships = relationshipBuilders.entrySet().stream().collect(
142+
var importResults = builders.entrySet().stream().collect(
147143
Collectors.toMap(
148144
Map.Entry::getKey,
149145
e -> e.getValue().build()
150146
));
151147

152148
return ImmutableRelationshipsAndProperties.builder()
153-
.relationships(relationships)
154-
.properties(relationshipPropertyStores)
155-
.directions(directions)
149+
.importResults(importResults)
156150
.build();
157151
}
158152

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
package org.neo4j.gds.core.loading;
21+
22+
import org.neo4j.gds.annotation.ValueClass;
23+
import org.neo4j.gds.api.RelationshipPropertyStore;
24+
import org.neo4j.gds.api.Relationships;
25+
import org.neo4j.gds.api.schema.Direction;
26+
27+
import java.util.Optional;
28+
29+
@ValueClass
30+
public interface SingleTypeRelationshipImportResult {
31+
32+
Direction direction();
33+
34+
Relationships.Topology forwardTopology();
35+
36+
Optional<RelationshipPropertyStore> forwardProperties();
37+
38+
Optional<Relationships.Topology> inverseTopology();
39+
40+
Optional<RelationshipPropertyStore> inverseProperties();
41+
}

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

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,19 @@ void shouldLoadInverseRelationships() {
9595

9696
var relationshipsAndProperties = importer.call();
9797

98-
var topology = relationshipsAndProperties.relationships().get(relationshipType);
99-
100-
assertThat(topology.inverseAdjacencyList()).isPresent();
101-
102-
var inverseAdjacencyList = topology.inverseAdjacencyList().get();
103-
104-
assertThat(degree("a", inverseAdjacencyList)).isEqualTo(1); // (c)-->(a)
105-
assertThat(degree("b", inverseAdjacencyList)).isEqualTo(1); // (a)-->(b)
106-
assertThat(degree("c", inverseAdjacencyList)).isEqualTo(2); // (a)-->(c),(b)-->(c)
107-
assertThat(degree("d", inverseAdjacencyList)).isEqualTo(1); // (a)-->(d)
108-
109-
assertThat(targets("a", inverseAdjacencyList)).isEqualTo(nodeIds("c")); // (c)-->(a)
110-
assertThat(targets("b", inverseAdjacencyList)).isEqualTo(nodeIds("a")); // (a)-->(b)
111-
assertThat(targets("c", inverseAdjacencyList)).isEqualTo(nodeIds("a", "b")); // (a)-->(c),(b)-->(c)
112-
assertThat(targets("d", inverseAdjacencyList)).isEqualTo(nodeIds("a")); // (a)-->(d)
98+
var singleTypeRelationshipImportResult = relationshipsAndProperties.importResults().get(relationshipType);
99+
assertThat(singleTypeRelationshipImportResult.inverseTopology()).isPresent();
100+
var adjacencyList = singleTypeRelationshipImportResult.inverseTopology().get().adjacencyList();
101+
102+
assertThat(degree("a", adjacencyList)).isEqualTo(1); // (c)-->(a)
103+
assertThat(degree("b", adjacencyList)).isEqualTo(1); // (a)-->(b)
104+
assertThat(degree("c", adjacencyList)).isEqualTo(2); // (a)-->(c),(b)-->(c)
105+
assertThat(degree("d", adjacencyList)).isEqualTo(1); // (a)-->(d)
106+
107+
assertThat(targets("a", adjacencyList)).isEqualTo(nodeIds("c")); // (c)-->(a)
108+
assertThat(targets("b", adjacencyList)).isEqualTo(nodeIds("a")); // (a)-->(b)
109+
assertThat(targets("c", adjacencyList)).isEqualTo(nodeIds("a", "b")); // (a)-->(c),(b)-->(c)
110+
assertThat(targets("d", adjacencyList)).isEqualTo(nodeIds("a")); // (a)-->(d)
113111
}
114112

115113
@Test
@@ -142,18 +140,22 @@ void shouldLoadInverseRelationshipsWithProperties() {
142140

143141
var relationshipsAndProperties = importer.call();
144142

145-
var adjacencyList = relationshipsAndProperties.relationships().get(relationshipType).adjacencyList();
146-
var properties = relationshipsAndProperties.properties()
147-
.get(relationshipType)
143+
var singleTypeRelationshipImportResult = relationshipsAndProperties.importResults().get(relationshipType);
144+
assertThat(singleTypeRelationshipImportResult.inverseTopology()).isPresent();
145+
assertThat(singleTypeRelationshipImportResult.inverseProperties()).isPresent();
146+
147+
var adjacencyList = singleTypeRelationshipImportResult.inverseTopology().get().adjacencyList();
148+
var propertyList = singleTypeRelationshipImportResult.inverseProperties().get()
149+
.relationshipProperties()
148150
.get("p")
149151
.values()
150152
.propertiesList();
151153

152154
//@formatter:off
153-
assertThat(properties("a", properties, adjacencyList::degree)).containsExactly(5.0); // (c)-[5.0]->(a)
154-
assertThat(properties("b", properties, adjacencyList::degree)).containsExactly(1.0); // (a)-[1.0]->(b)
155-
assertThat(properties("c", properties, adjacencyList::degree)).containsExactly(2.0, 4.0); // (a)-[2.0]->(c),(b)-[4.0]->(c)
156-
assertThat(properties("d", properties, adjacencyList::degree)).containsExactly(3.0); // (a)-[3.0->(d)
155+
assertThat(properties("a", propertyList, adjacencyList::degree)).containsExactly(5.0); // (c)-[5.0]->(a)
156+
assertThat(properties("b", propertyList, adjacencyList::degree)).containsExactly(1.0); // (a)-[1.0]->(b)
157+
assertThat(properties("c", propertyList, adjacencyList::degree)).containsExactly(2.0, 4.0); // (a)-[2.0]->(c),(b)-[4.0]->(c)
158+
assertThat(properties("d", propertyList, adjacencyList::degree)).containsExactly(3.0); // (a)-[3.0->(d)
157159
//@formatter:on
158160
}
159161

0 commit comments

Comments
 (0)