Skip to content

Commit 0f76bba

Browse files
committed
Extend node property mapping validation to default values
1 parent 4c4dbf8 commit 0f76bba

File tree

3 files changed

+80
-29
lines changed

3 files changed

+80
-29
lines changed

doc/modules/ROOT/pages/machine-learning/linkprediction-pipelines/training.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ CALL gds.graph.project(
312312
'fullGraph',
313313
{
314314
Person: {
315-
properties: ['age']
315+
properties: {age: {defaultValue: 1}}
316316
},
317317
City: {
318318
properties: {age: {defaultValue: 1}}

graph-projection-api/src/main/java/org/neo4j/gds/AbstractNodeProjections.java

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.HashMap;
2828
import java.util.LinkedHashMap;
2929
import java.util.Map;
30+
import java.util.Objects;
3031

3132
import static java.util.Collections.emptyMap;
3233
import static java.util.Collections.singletonMap;
@@ -175,23 +176,39 @@ private static void validateIdentifierName(String identifier) {
175176

176177
@Value.Check
177178
public void validatePropertyKeyMappings() {
178-
var mapping = new HashMap<String, String>();
179+
var seenMappings = new HashMap<String, PropertyMapping>();
179180

180181
projections().values().stream()
181182
.flatMap(nodeProjection -> nodeProjection.properties().stream())
182183
.forEach(propertyMapping -> {
183184
var propertyKey = propertyMapping.propertyKey();
184-
var neoKey = propertyMapping.neoPropertyKey();
185-
186-
if (mapping.containsKey(propertyKey) && !mapping.get(propertyKey).equals(neoKey)) {
187-
throw new IllegalArgumentException(formatWithLocale(
188-
"Specifying multiple neoPropertyKeys for the same property is not allowed, found propertyKey: %s, neoPropertyKeys: %s, %s.",
189-
propertyKey,
190-
neoKey,
191-
mapping.get(propertyKey)
192-
));
185+
186+
if (seenMappings.containsKey(propertyKey)) {
187+
// we have another mapping with the same GDS key
188+
var seenMapping = seenMappings.get(propertyKey);
189+
190+
if (!Objects.equals(seenMapping.neoPropertyKey(), propertyMapping.neoPropertyKey())) {
191+
throw new IllegalArgumentException(formatWithLocale(
192+
"Specifying multiple neoPropertyKeys for the same property is not allowed, " +
193+
"found propertyKey: `%s` with conflicting neoPropertyKeys: `%s`, `%s`.",
194+
propertyKey,
195+
propertyMapping.neoPropertyKey(),
196+
seenMapping.neoPropertyKey()
197+
));
198+
}
199+
200+
if (!Objects.equals(seenMapping.defaultValue(), propertyMapping.defaultValue())) {
201+
throw new IllegalArgumentException(formatWithLocale(
202+
"Specifying different default values for the same property with identical neoPropertyKey is not allowed, " +
203+
"found propertyKey: `%s` with conflicting default values: `%s`, `%s`.",
204+
propertyKey,
205+
propertyMapping.defaultValue().getObject(),
206+
seenMapping.defaultValue().getObject()
207+
));
208+
}
193209
}
194-
mapping.put(propertyKey, neoKey);
210+
211+
seenMappings.put(propertyKey, propertyMapping);
195212
});
196213
}
197214
}

graph-projection-api/src/test/java/org/neo4j/gds/NodeProjectionsTest.java

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,37 +147,71 @@ void shouldFailOnDuplicatePropertyKeys() {
147147
.hasMessage("Duplicate property key `prop`");
148148
}
149149

150-
@Test
151-
void shouldFailOnAmbiguousNodePropertyDefinition() {
152-
var builder = NodeProjections.builder().projections(Map.of(
150+
static Stream<Arguments> ambiguousPropertyMappings() {
151+
return Stream.of(
152+
Arguments.of(
153+
"different neo key",
154+
PropertyMapping.of("foo", "bar", DefaultValue.DEFAULT),
155+
PropertyMapping.of("foo", "baz", DefaultValue.DEFAULT),
156+
new String[]{
157+
"Specifying multiple neoPropertyKeys for the same property is not allowed, found propertyKey: `foo` with conflicting neoPropertyKeys:",
158+
"`bar`",
159+
"`baz`"
160+
}
161+
),
162+
Arguments.of(
163+
"different default value types",
164+
PropertyMapping.of("foo", "baz", DefaultValue.forLong()),
165+
PropertyMapping.of("foo", "baz", DefaultValue.forDouble()),
166+
new String[]{
167+
"Specifying different default values for the same property with identical neoPropertyKey is not allowed, found propertyKey: `foo` with conflicting default values:",
168+
"`-9223372036854775808`",
169+
"`NaN`",
170+
}
171+
),
172+
Arguments.of(
173+
"different default values",
174+
PropertyMapping.of("foo", "baz", DefaultValue.of(42)),
175+
PropertyMapping.of("foo", "baz", DefaultValue.of(1337)),
176+
new String[]{
177+
"Specifying different default values for the same property with identical neoPropertyKey is not allowed, found propertyKey: `foo` with conflicting default values:",
178+
"`42`",
179+
"`1337`",
180+
}
181+
)
182+
);
183+
}
184+
185+
@ParameterizedTest(name = "{0}")
186+
@MethodSource("ambiguousPropertyMappings")
187+
void shouldFailOnAmbiguousNodePropertyDefinition(
188+
String ignore,
189+
PropertyMapping first,
190+
PropertyMapping second,
191+
String... messages
192+
) {
193+
var builder = ImmutableNodeProjections.builder().projections(Map.of(
153194
NodeLabel.of("A"),
154195
NodeProjection
155196
.builder()
156197
.label("A")
157-
.properties(PropertyMappings
158-
.builder()
159-
.addMapping(PropertyMapping.of("foo", "bar", DefaultValue.DEFAULT))
160-
.build()
161-
)
198+
.addProperty(first)
162199
.build(),
163200

164201
NodeLabel.of("B"),
165202
NodeProjection
166203
.builder()
167204
.label("B")
168-
.properties(PropertyMappings
169-
.builder()
170-
.addMapping(PropertyMapping.of("foo", "baz", DefaultValue.DEFAULT))
171-
.build()
172-
)
205+
.addProperty(second)
173206
.build()
174207

175208
));
176209

177-
assertThatThrownBy(builder::build)
178-
.isInstanceOf(IllegalArgumentException.class)
179-
.hasMessageContaining(
180-
"Specifying multiple neoPropertyKeys for the same property is not allowed, found propertyKey: foo, neoPropertyKeys");
210+
var throwableAssert = assertThatThrownBy(builder::build)
211+
.isInstanceOf(IllegalArgumentException.class);
212+
for (var message : messages) {
213+
throwableAssert.hasMessageContaining(message);
214+
}
181215
}
182216

183217
@Test

0 commit comments

Comments
 (0)