2121
2222import org .jetbrains .annotations .NotNull ;
2323import org .jetbrains .annotations .Nullable ;
24+ import org .jetbrains .annotations .TestOnly ;
2425import org .neo4j .gds .NodeLabel ;
2526import org .neo4j .gds .Orientation ;
2627import org .neo4j .gds .RelationshipType ;
3940import org .neo4j .gds .api .schema .RelationshipSchema ;
4041import org .neo4j .gds .config .GraphProjectConfig ;
4142import org .neo4j .gds .core .Aggregation ;
43+ import org .neo4j .gds .core .ConfigKeyValidation ;
4244import org .neo4j .gds .core .CypherMapWrapper ;
4345import org .neo4j .gds .core .Username ;
4446import org .neo4j .gds .core .compress .AdjacencyCompressor ;
8789import java .util .Optional ;
8890import java .util .Set ;
8991import java .util .concurrent .ConcurrentHashMap ;
92+ import java .util .concurrent .atomic .AtomicBoolean ;
9093import java .util .concurrent .locks .Lock ;
9194import java .util .concurrent .locks .ReentrantLock ;
9295
@@ -121,6 +124,8 @@ public static class GraphAggregator {
121124 private final DatabaseId databaseId ;
122125 private final String username ;
123126
127+ private final ConfigValidator configValidator ;
128+
124129 // #result() may be called twice, we cache the result of the first call to return it again in the second invocation
125130 private @ Nullable AggregationResult result ;
126131
@@ -137,6 +142,7 @@ public static class GraphAggregator {
137142 this .databaseId = databaseId ;
138143 this .username = username ;
139144 this .lock = new ReentrantLock ();
145+ this .configValidator = new ConfigValidator ();
140146 }
141147
142148 @ UserAggregationUpdate
@@ -168,15 +174,7 @@ public void update(
168174 targetNodeLabels = labelsConfig ("targetNodeLabels" , (MapValue ) nodesConfig );
169175 }
170176
171- // TODO:
172- // if (!nodesConfig.isEmpty()) {
173- // CypherMapWrapper.create(nodesConfig).requireOnlyKeysFrom(List.of(
174- // "sourceNodeProperties",
175- // "sourceNodeLabels",
176- // "targetNodeProperties",
177- // "targetNodeLabels"
178- // ));
179- // }
177+ this .configValidator .validateNodesConfig ((MapValue ) nodesConfig );
180178 }
181179
182180 var data = initGraphData (
@@ -196,7 +194,8 @@ public void update(
196194 targetNodePropertyValues ,
197195 sourceNodeLabels ,
198196 targetNodeLabels ,
199- relationshipConfig
197+ relationshipConfig ,
198+ this .configValidator
200199 );
201200 }
202201
@@ -238,7 +237,6 @@ private LazyImporter initGraphData(
238237 }
239238
240239 private static NodeLabelToken labelsConfig (String nodeLabelKey , @ NotNull MapValue nodesConfig ) {
241- // TODO: was remove
242240 var nodeLabelsEntry = nodesConfig .get (nodeLabelKey );
243241 return tryLabelsConfig (nodeLabelsEntry , nodeLabelKey );
244242 }
@@ -400,7 +398,8 @@ void update(
400398 @ Nullable MapValue targetNodePropertyValues ,
401399 NodeLabelToken sourceNodeLabels ,
402400 NodeLabelToken targetNodeLabels ,
403- AnyValue relationshipConfig
401+ AnyValue relationshipConfig ,
402+ ConfigValidator configValidator
404403 ) {
405404 MapValue relationshipProperties = null ;
406405 RelationshipType relationshipType = RelationshipType .ALL_RELATIONSHIPS ;
@@ -409,13 +408,7 @@ void update(
409408 relationshipProperties = propertiesConfig ("properties" , (MapValue ) relationshipConfig );
410409 relationshipType = typeConfig ("relationshipType" , (MapValue ) relationshipConfig );
411410
412- // TODO:
413- // if (!relationshipConfig.isEmpty()) {
414- // CypherMapWrapper.create(relationshipConfig).requireOnlyKeysFrom(List.of(
415- // "properties",
416- // "relationshipType"
417- // ));
418- // }
411+ configValidator .validateRelationshipsConfig ((MapValue ) relationshipConfig );
419412 }
420413
421414 var intermediateSourceId = loadNode (sourceNode , sourceNodeLabels , sourceNodePropertyValues );
@@ -678,7 +671,6 @@ private static MapValue propertiesConfig(
678671 String propertyKey ,
679672 @ NotNull MapValue propertiesConfig
680673 ) {
681- // TODO: was remove
682674 var nodeProperties = propertiesConfig .get (propertyKey );
683675 if (nodeProperties instanceof MapValue ) {
684676 return (MapValue ) nodeProperties ;
@@ -698,7 +690,6 @@ private static RelationshipType typeConfig(
698690 @ SuppressWarnings ("SameParameterValue" ) String relationshipTypeKey ,
699691 @ NotNull MapValue relationshipConfig
700692 ) {
701- // TODO: was remove
702693 var relationshipTypeEntry = relationshipConfig .get (relationshipTypeKey );
703694 if (relationshipTypeEntry instanceof TextValue ) {
704695 return RelationshipType .of (((TextValue ) relationshipTypeEntry ).stringValue ());
@@ -719,6 +710,34 @@ private static long extractNodeId(@NotNull AnyValue node) {
719710 }
720711 }
721712
713+ private static final class ConfigValidator {
714+ private static final Set <String > NODES_CONFIG_KEYS = Set .of (
715+ "sourceNodeProperties" ,
716+ "sourceNodeLabels" ,
717+ "targetNodeProperties" ,
718+ "targetNodeLabels"
719+ );
720+
721+ private static final Set <String > RELATIONSHIPS_CONFIG_KEYS = Set .of (
722+ "properties" ,
723+ "relationshipType"
724+ );
725+
726+ private final AtomicBoolean validateNodes = new AtomicBoolean (true );
727+ private final AtomicBoolean validateRelationships = new AtomicBoolean (true );
728+
729+ void validateNodesConfig (MapValue nodesConfig ) {
730+ if (this .validateNodes .getAndSet (false )) {
731+ ConfigKeyValidation .requireOnlyKeysFrom (NODES_CONFIG_KEYS , nodesConfig .keySet ());
732+ }
733+ }
734+
735+ void validateRelationshipsConfig (MapValue relationshipConfig ) {
736+ if (this .validateRelationships .getAndSet (false )) {
737+ ConfigKeyValidation .requireOnlyKeysFrom (RELATIONSHIPS_CONFIG_KEYS , relationshipConfig .keySet ());
738+ }
739+ }
740+ }
722741
723742 @ ValueClass
724743 @ Configuration
@@ -770,6 +789,7 @@ static GraphProjectFromCypherAggregationConfig of(String userName, String graphN
770789 );
771790 }
772791
792+ @ TestOnly
773793 static GraphProjectFromCypherAggregationConfig of (
774794 String userName ,
775795 String graphName ,
0 commit comments