Skip to content

Commit 038c24b

Browse files
committed
Add support for AnyValue based APIs throughout loading code
1 parent ac9dadd commit 038c24b

File tree

6 files changed

+165
-41
lines changed

6 files changed

+165
-41
lines changed

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

Lines changed: 5 additions & 7 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.jetbrains.annotations.NotNull;
2223
import org.jetbrains.annotations.Nullable;
2324
import org.neo4j.gds.annotation.ValueClass;
2425
import org.neo4j.gds.api.IdMap;
@@ -28,8 +29,8 @@
2829
import org.neo4j.gds.core.loading.construction.NodeLabelToken;
2930
import org.neo4j.gds.core.loading.construction.NodeLabelTokens;
3031
import org.neo4j.gds.core.loading.construction.NodesBuilder;
32+
import org.neo4j.gds.core.loading.construction.PropertyValues;
3133
import org.neo4j.gds.core.utils.paged.ShardedLongLongMap;
32-
import org.neo4j.values.storable.Value;
3334

3435
import java.util.Map;
3536
import java.util.Optional;
@@ -74,8 +75,8 @@ public long addNode(long nodeId, @Nullable NodeLabelToken nodeLabels) {
7475

7576
public long addNodeWithProperties(
7677
long nodeId,
77-
Map<String, Value> properties,
78-
@Nullable NodeLabelToken nodeLabels
78+
PropertyValues properties,
79+
@NotNull NodeLabelToken nodeLabels
7980
) {
8081
var intermediateId = this.intermediateIdMapBuilder.toMappedNodeId(nodeId);
8182

@@ -86,13 +87,10 @@ public long addNodeWithProperties(
8687

8788
intermediateId = this.intermediateIdMapBuilder.addNode(nodeId);
8889
isEmpty.lazySet(false);
89-
if (nodeLabels == null) {
90-
nodeLabels = NodeLabelTokens.empty();
91-
}
9290
if (properties.isEmpty()) {
9391
this.nodesBuilder.addNode(intermediateId, nodeLabels);
9492
} else {
95-
this.nodesBuilder.addNode(intermediateId, properties, nodeLabels);
93+
this.nodesBuilder.addNode(intermediateId, nodeLabels, properties);
9694
}
9795

9896
return intermediateId;

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.neo4j.gds.core.Aggregation;
2323
import org.neo4j.internal.kernel.api.PropertyCursor;
24+
import org.neo4j.values.AnyValue;
2425
import org.neo4j.values.storable.NumberValue;
2526
import org.neo4j.values.storable.Value;
2627
import org.neo4j.values.storable.Values;
@@ -69,11 +70,11 @@ public static void readProperties(
6970
}
7071
}
7172

72-
public static double extractValue(Value value, double defaultValue) {
73+
public static double extractValue(AnyValue value, double defaultValue) {
7374
return extractValue(Aggregation.NONE, value, defaultValue);
7475
}
7576

76-
public static double extractValue(Aggregation aggregation, Value value, double defaultValue) {
77+
public static double extractValue(Aggregation aggregation, AnyValue value, double defaultValue) {
7778
// slightly different logic than org.neo4j.values.storable.Values#coerceToDouble
7879
// b/c we want to fallback to the default value if the value is empty
7980
if (value instanceof NumberValue) {
@@ -88,7 +89,7 @@ public static double extractValue(Aggregation aggregation, Value value, double d
8889
// Do we want to do so or is failing on non numeric properties ok?
8990
throw new IllegalArgumentException(formatWithLocale(
9091
"Unsupported type [%s] of value %s. Please use a numeric property.",
91-
value.valueGroup(),
92+
value.getTypeName(),
9293
value
9394
));
9495
}

core/src/main/java/org/neo4j/gds/core/loading/construction/NodeLabelTokens.java

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
*/
2020
package org.neo4j.gds.core.loading.construction;
2121

22+
import org.eclipse.collections.api.block.function.primitive.ObjectIntToObjectFunction;
2223
import org.jetbrains.annotations.Contract;
2324
import org.jetbrains.annotations.NotNull;
2425
import org.jetbrains.annotations.Nullable;
2526
import org.neo4j.gds.NodeLabel;
2627
import org.neo4j.graphdb.Label;
2728
import org.neo4j.values.SequenceValue;
29+
import org.neo4j.values.storable.TextArray;
2830
import org.neo4j.values.storable.TextValue;
2931

3032
import java.util.Arrays;
@@ -48,32 +50,16 @@ public final class NodeLabelTokens {
4850
return nodeLabelToken;
4951
}
5052

53+
public static NodeLabelToken of(TextValue textValue) {
54+
return new Singleton<>(textValue, tv -> NodeLabelTokens.labelOf(tv.stringValue()));
55+
}
56+
57+
public static NodeLabelToken of(TextArray textArray) {
58+
return new Sequence<>(textArray, TextArray::stringValue);
59+
}
60+
5161
public static @NotNull NodeLabelToken of(SequenceValue nodeLabels) {
52-
return new NodeLabelToken() {
53-
@Override
54-
public boolean isEmpty() {
55-
return nodeLabels.isEmpty();
56-
}
57-
58-
@Override
59-
public int size() {
60-
return nodeLabels.length();
61-
}
62-
63-
@Override
64-
public @NotNull NodeLabel get(int index) {
65-
return new NodeLabel(((TextValue) nodeLabels.value(index)).stringValue());
66-
}
67-
68-
@Override
69-
public String[] getStrings() {
70-
var result = new String[nodeLabels.length()];
71-
for (int i = 0; i < nodeLabels.length(); i++) {
72-
result[i] = ( (TextValue) nodeLabels.value(i)).stringValue();
73-
}
74-
return result;
75-
}
76-
};
62+
return new Sequence<>(nodeLabels, (s, i) -> ((TextValue) s.value(i)).stringValue());
7763
}
7864

7965
@Contract("null -> !null")
@@ -281,6 +267,51 @@ public int hashCode() {
281267
}
282268
}
283269

270+
private static final class Sequence<T extends SequenceValue> implements NodeLabelToken {
271+
private final T sequence;
272+
private final ObjectIntToObjectFunction<T, String> toString;
273+
274+
private Sequence(T sequence, ObjectIntToObjectFunction<T, String> toString) {
275+
this.sequence = sequence;
276+
this.toString = toString;
277+
}
278+
279+
@Override
280+
public boolean isEmpty() {
281+
return sequence.isEmpty();
282+
}
283+
284+
@Override
285+
public int size() {
286+
return sequence.length();
287+
}
288+
289+
@Override
290+
public @NotNull NodeLabel get(int index) {
291+
return new NodeLabel(toString.valueOf(sequence, index));
292+
}
293+
294+
@Override
295+
public String[] getStrings() {
296+
var result = new String[sequence.length()];
297+
Arrays.setAll(result, i -> toString.valueOf(sequence, i));
298+
return result;
299+
}
300+
301+
@Override
302+
public boolean equals(Object o) {
303+
if (this == o) return true;
304+
if (o == null || getClass() != o.getClass()) return false;
305+
Sequence<?> sequence1 = (Sequence<?>) o;
306+
return sequence.equals(sequence1.sequence);
307+
}
308+
309+
@Override
310+
public int hashCode() {
311+
return Objects.hash(sequence);
312+
}
313+
}
314+
284315
private static final class Singleton<T> implements NodeLabelToken {
285316

286317
private final NodeLabel item;

core/src/main/java/org/neo4j/gds/core/loading/construction/NodesBuilder.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.neo4j.gds.core.utils.paged.HugeAtomicGrowingBitSet;
4242
import org.neo4j.gds.utils.AutoCloseableThreadLocal;
4343
import org.neo4j.values.storable.Value;
44+
import org.neo4j.values.virtual.MapValue;
4445

4546
import java.util.ArrayList;
4647
import java.util.Collections;
@@ -175,7 +176,11 @@ public void addNode(long originalId, Map<String, Value> properties) {
175176
}
176177

177178
public void addNode(long originalId, Map<String, Value> properties, NodeLabelToken nodeLabels) {
178-
this.threadLocalBuilder.get().addNode(originalId, properties, nodeLabels);
179+
this.addNode(originalId, nodeLabels, PropertyValues.of(properties));
180+
}
181+
182+
public void addNode(long originalId, MapValue properties, NodeLabelToken nodeLabels) {
183+
this.addNode(originalId, nodeLabels, PropertyValues.of(properties));
179184
}
180185

181186
public void addNode(long originalId, Map<String, Value> properties, NodeLabel... nodeLabels) {
@@ -186,6 +191,10 @@ public void addNode(long originalId, Map<String, Value> properties, NodeLabel no
186191
this.addNode(originalId, properties, NodeLabelTokens.ofNodeLabel(nodeLabel));
187192
}
188193

194+
public void addNode(long originalId, NodeLabelToken nodeLabels, PropertyValues properties) {
195+
this.threadLocalBuilder.get().addNode(originalId, nodeLabels, properties);
196+
}
197+
189198
public long importedNodes() {
190199
return this.importedNodes.sum();
191200
}
@@ -280,7 +289,7 @@ private static class ThreadLocalBuilder implements AutoCloseable {
280289
private final Function<NodeLabel, Integer> labelTokenIdFn;
281290
private final Function<String, NodePropertiesFromStoreBuilder> propertyBuilderFn;
282291
private final NodeImporter nodeImporter;
283-
private final List<Map<String, Value>> batchNodeProperties;
292+
private final List<PropertyValues> batchNodeProperties;
284293

285294
ThreadLocalBuilder(
286295
LongAdder importedNodes,
@@ -319,7 +328,7 @@ public void addNode(long originalId, NodeLabelToken nodeLabels) {
319328
}
320329
}
321330

322-
public void addNode(long originalId, Map<String, Value> properties, NodeLabelToken nodeLabels) {
331+
public void addNode(long originalId, NodeLabelToken nodeLabels, PropertyValues properties) {
323332
if (!seenNodeIdPredicate.test(originalId)) {
324333
long[] labels = labelTokens(nodeLabels);
325334

@@ -358,8 +367,8 @@ private void flushBuffer() {
358367
(nodeReference, labelIds, propertiesReference) -> {
359368
if (!propertiesReference.isEmpty()) {
360369
var propertyValueIndex = (int) ((LongPropertyReference) propertiesReference).id;
361-
Map<String, Value> properties = batchNodeProperties.get(propertyValueIndex);
362-
MutableInt importedProperties = new MutableInt(0);
370+
var properties = batchNodeProperties.get(propertyValueIndex);
371+
var importedProperties = new MutableInt(0);
363372
properties.forEach((propertyKey, propertyValue) -> importedProperties.add(importProperty(
364373
nodeReference,
365374
propertyKey,
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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.construction;
21+
22+
import org.neo4j.values.storable.Value;
23+
import org.neo4j.values.virtual.MapValue;
24+
25+
import java.util.Map;
26+
import java.util.function.BiConsumer;
27+
28+
public abstract class PropertyValues {
29+
30+
public abstract void forEach(BiConsumer<String, Value> consumer);
31+
32+
public abstract boolean isEmpty();
33+
34+
public static PropertyValues of(MapValue mapValue) {
35+
return new CypherPropertyValues(mapValue);
36+
}
37+
38+
public static PropertyValues of(Map<String, Value> map) {
39+
return new NativePropertyValues(map);
40+
}
41+
42+
private static final class NativePropertyValues extends PropertyValues {
43+
private final Map<String, Value> properties;
44+
45+
private NativePropertyValues(Map<String, Value> properties) {
46+
this.properties = properties;
47+
}
48+
49+
@Override
50+
public void forEach(BiConsumer<String, Value> consumer) {
51+
this.properties.forEach(consumer);
52+
}
53+
54+
@Override
55+
public boolean isEmpty() {
56+
return this.properties.isEmpty();
57+
}
58+
}
59+
60+
private static final class CypherPropertyValues extends PropertyValues {
61+
private final MapValue properties;
62+
63+
private CypherPropertyValues(MapValue properties) {
64+
this.properties = properties;
65+
}
66+
67+
@Override
68+
public void forEach(BiConsumer<String, Value> consumer) {
69+
this.properties.foreach((k, v) -> {
70+
if (v instanceof Value) {
71+
consumer.accept(k, (Value) v);
72+
}
73+
});
74+
}
75+
76+
@Override
77+
public boolean isEmpty() {
78+
return this.properties.isEmpty();
79+
}
80+
}
81+
}

cypher-aggregation/src/main/java/org/neo4j/gds/projection/CypherAggregation.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,11 @@ private long loadNode(
511511

512512
return nodeProperties == null
513513
? this.idMapBuilder.addNode(originalNodeId, nodeLabels)
514-
: this.idMapBuilder.addNodeWithProperties(originalNodeId, nodeProperties, nodeLabels);
514+
: this.idMapBuilder.addNodeWithProperties(
515+
originalNodeId,
516+
PropertyValues.of(nodeProperties),
517+
nodeLabels
518+
);
515519
}
516520

517521
private static double loadOneRelationshipProperty(

0 commit comments

Comments
 (0)