diff --git a/algo-common/src/main/java/org/neo4j/gds/applications/algorithms/machinery/AlgorithmLabel.java b/algo-common/src/main/java/org/neo4j/gds/applications/algorithms/machinery/AlgorithmLabel.java
index 74ab2640396..d5da2fb3439 100644
--- a/algo-common/src/main/java/org/neo4j/gds/applications/algorithms/machinery/AlgorithmLabel.java
+++ b/algo-common/src/main/java/org/neo4j/gds/applications/algorithms/machinery/AlgorithmLabel.java
@@ -68,6 +68,7 @@ public enum AlgorithmLabel implements Label {
MCMF("MinCostMaxFlow"),
Modularity("Modularity"),
ModularityOptimization("ModularityOptimization"),
+ NodeCount("NodeCount"),
NodeSimilarity("Node Similarity"),
Node2Vec("Node2Vec"),
PageRank("PageRank"),
diff --git a/algo-params/miscellaneous-params/src/main/java/org/neo4j/gds/nodecount/NodeCountParameters.java b/algo-params/miscellaneous-params/src/main/java/org/neo4j/gds/nodecount/NodeCountParameters.java
new file mode 100644
index 00000000000..2c0c270e088
--- /dev/null
+++ b/algo-params/miscellaneous-params/src/main/java/org/neo4j/gds/nodecount/NodeCountParameters.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+import org.neo4j.gds.AlgorithmParameters;
+import org.neo4j.gds.core.concurrency.Concurrency;
+
+/**
+ * The immutable, user-input-free parameters the algorithm actually runs on.
+ * Node counting needs nothing but the concurrency that the surrounding machinery expects; a real algorithm
+ * would carry its tuning knobs here, derived from the configuration via {@code toParameters()}.
+ */
+public record NodeCountParameters(Concurrency concurrency) implements AlgorithmParameters {
+}
diff --git a/algo/src/main/java/org/neo4j/gds/MiscellaneousAlgorithmsTasks.java b/algo/src/main/java/org/neo4j/gds/MiscellaneousAlgorithmsTasks.java
index 91ec06868d6..cde92217d1d 100644
--- a/algo/src/main/java/org/neo4j/gds/MiscellaneousAlgorithmsTasks.java
+++ b/algo/src/main/java/org/neo4j/gds/MiscellaneousAlgorithmsTasks.java
@@ -24,6 +24,7 @@
import org.neo4j.gds.core.utils.progress.tasks.Task;
import org.neo4j.gds.indexInverse.InverseRelationshipsTask;
import org.neo4j.gds.indexinverse.InverseRelationshipsParameters;
+import org.neo4j.gds.nodecount.NodeCountTask;
import org.neo4j.gds.scaleproperties.ScalePropertiesParameters;
import org.neo4j.gds.scaleproperties.ScalePropertiesTask;
import org.neo4j.gds.undirected.ToUndirectedTask;
@@ -31,6 +32,10 @@
public final class MiscellaneousAlgorithmsTasks {
private MiscellaneousAlgorithmsTasks() {}
+ public static Task nodeCount(Graph graph){
+ return NodeCountTask.create(graph);
+ }
+
public static Task scaleProperties(Graph graph, ScalePropertiesParameters parameters){
return ScalePropertiesTask.create(graph, parameters);
}
diff --git a/algo/src/main/java/org/neo4j/gds/nodecount/NodeCount.java b/algo/src/main/java/org/neo4j/gds/nodecount/NodeCount.java
new file mode 100644
index 00000000000..1d6a54505d5
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/nodecount/NodeCount.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+import org.neo4j.gds.Algorithm;
+import org.neo4j.gds.api.Graph;
+import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
+import org.neo4j.gds.termination.TerminationFlag;
+
+/**
+ * A deliberately trivial example algorithm: it counts the number of nodes in the graph and returns it.
+ *
+ * Its only purpose is to act as a template that shows the full set of modules and wiring required to add a
+ * new algorithm to GDS, without the distraction of any real algorithmic complexity. To implement a real
+ * algorithm, replace the body of {@link #compute()} with the actual computation; everything around it
+ * (config, parameters, facades, procedures, progress tracking) follows the same shape demonstrated here.
+ */
+public class NodeCount extends Algorithm {
+
+ private final Graph graph;
+
+ public NodeCount(Graph graph, ProgressTracker progressTracker, TerminationFlag terminationFlag) {
+ super(progressTracker);
+ this.graph = graph;
+ this.terminationFlag = terminationFlag;
+ }
+
+ @Override
+ public NodeCountResult compute() {
+ progressTracker.beginSubTask();
+
+ terminationFlag.assertRunning();
+ var nodeCount = graph.nodeCount();
+ progressTracker.onProgress(nodeCount);
+
+ progressTracker.endSubTask();
+
+ return new NodeCountResult(nodeCount);
+ }
+}
diff --git a/algo/src/main/java/org/neo4j/gds/nodecount/NodeCountMemoryEstimateDefinition.java b/algo/src/main/java/org/neo4j/gds/nodecount/NodeCountMemoryEstimateDefinition.java
new file mode 100644
index 00000000000..84ccbcd9096
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/nodecount/NodeCountMemoryEstimateDefinition.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+import org.neo4j.gds.mem.MemoryEstimateDefinition;
+import org.neo4j.gds.mem.MemoryEstimation;
+import org.neo4j.gds.mem.MemoryEstimations;
+
+public class NodeCountMemoryEstimateDefinition implements MemoryEstimateDefinition {
+
+ @Override
+ public MemoryEstimation memoryEstimation() {
+ // Counting nodes only requires holding on to a single long, independent of the graph size.
+ return MemoryEstimations.builder(NodeCount.class)
+ .fixed("node count", Long.BYTES)
+ .build();
+ }
+}
diff --git a/algo/src/main/java/org/neo4j/gds/nodecount/NodeCountResult.java b/algo/src/main/java/org/neo4j/gds/nodecount/NodeCountResult.java
new file mode 100644
index 00000000000..fb6f5734a41
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/nodecount/NodeCountResult.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+public record NodeCountResult(long nodeCount) {
+}
diff --git a/algo/src/main/java/org/neo4j/gds/nodecount/NodeCountTask.java b/algo/src/main/java/org/neo4j/gds/nodecount/NodeCountTask.java
new file mode 100644
index 00000000000..dc1183cf2cd
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/nodecount/NodeCountTask.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+import org.neo4j.gds.api.Graph;
+import org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel;
+import org.neo4j.gds.core.utils.progress.tasks.Task;
+import org.neo4j.gds.core.utils.progress.tasks.Tasks;
+
+public final class NodeCountTask {
+ private NodeCountTask() {}
+
+ public static Task create(Graph graph) {
+ return Tasks.leaf(AlgorithmLabel.NodeCount.asString(), graph.nodeCount());
+ }
+}
diff --git a/algo/src/test/java/org/neo4j/gds/nodecount/NodeCountTest.java b/algo/src/test/java/org/neo4j/gds/nodecount/NodeCountTest.java
new file mode 100644
index 00000000000..70a3ab8849e
--- /dev/null
+++ b/algo/src/test/java/org/neo4j/gds/nodecount/NodeCountTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.neo4j.gds.MiscellaneousAlgorithmsTasks;
+import org.neo4j.gds.TestGraph;
+import org.neo4j.gds.TestProgressTrackerHelper;
+import org.neo4j.gds.core.concurrency.Concurrency;
+import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
+import org.neo4j.gds.extension.GdlExtension;
+import org.neo4j.gds.extension.GdlGraph;
+import org.neo4j.gds.extension.Inject;
+import org.neo4j.gds.termination.TerminationFlag;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.neo4j.gds.assertj.Extractors.removingThreadId;
+import static org.neo4j.gds.compat.TestLog.INFO;
+
+@GdlExtension
+class NodeCountTest {
+
+ @GdlGraph
+ static String GDL =
+ "(a)-->(b)" +
+ ",(b)-->(c)" +
+ ",(c)-->(a)";
+
+ @Inject
+ private TestGraph graph;
+
+ @Test
+ void countsNodes() {
+ var algorithm = new NodeCount(graph, ProgressTracker.NULL_TRACKER, TerminationFlag.RUNNING_TRUE);
+
+ var result = algorithm.compute();
+
+ assertEquals(graph.nodeCount(), result.nodeCount());
+ assertEquals(3L, result.nodeCount());
+ }
+
+ @Test
+ void progressLogging() {
+ var progressTrackerWithLog = TestProgressTrackerHelper.create(
+ MiscellaneousAlgorithmsTasks.nodeCount(graph),
+ new Concurrency(1)
+ );
+
+ var progressTracker = progressTrackerWithLog.progressTracker();
+ var log = progressTrackerWithLog.log();
+
+ new NodeCount(graph, progressTracker, TerminationFlag.RUNNING_TRUE).compute();
+
+ Assertions.assertThat(log.getMessages(INFO))
+ // avoid asserting on the thread id
+ .extracting(removingThreadId())
+ .contains(
+ "NodeCount :: Start",
+ "NodeCount :: Finished"
+ );
+ }
+}
diff --git a/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousAlgorithms.java b/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousAlgorithms.java
index 3f774bce83a..4da9a8e87f5 100644
--- a/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousAlgorithms.java
+++ b/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousAlgorithms.java
@@ -28,6 +28,8 @@
import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
import org.neo4j.gds.indexInverse.InverseRelationships;
import org.neo4j.gds.indexinverse.InverseRelationshipsParameters;
+import org.neo4j.gds.nodecount.NodeCount;
+import org.neo4j.gds.nodecount.NodeCountResult;
import org.neo4j.gds.scaleproperties.ScaleProperties;
import org.neo4j.gds.scaleproperties.ScalePropertiesParameters;
import org.neo4j.gds.scaleproperties.ScalePropertiesResult;
@@ -46,6 +48,12 @@ public MiscellaneousAlgorithms( TerminationFlag terminationFlag) {
this.terminationFlag = terminationFlag;
}
+ public NodeCountResult nodeCount(Graph graph, ProgressTracker progressTracker) {
+ var algorithm = new NodeCount(graph, progressTracker, terminationFlag);
+
+ return algorithm.compute();
+ }
+
public SingleTypeRelationships collapsePath(GraphStore graphStore, CollapsePathParameters parameters) {
var algorithm = CollapsePath.create(
diff --git a/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousAlgorithmsBusinessFacade.java b/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousAlgorithmsBusinessFacade.java
index ca19d68afd2..0950c2ddcc0 100644
--- a/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousAlgorithmsBusinessFacade.java
+++ b/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousAlgorithmsBusinessFacade.java
@@ -29,6 +29,8 @@
import org.neo4j.gds.core.loading.SingleTypeRelationships;
import org.neo4j.gds.indexInverse.InverseRelationshipsConfig;
import org.neo4j.gds.indexInverse.InverseRelationshipsParamsTransformer;
+import org.neo4j.gds.nodecount.NodeCountBaseConfig;
+import org.neo4j.gds.nodecount.NodeCountResult;
import org.neo4j.gds.scaleproperties.ScalePropertiesBaseConfig;
import org.neo4j.gds.scaleproperties.ScalePropertiesResult;
import org.neo4j.gds.undirected.ToUndirectedConfig;
@@ -48,6 +50,18 @@ public MiscellaneousAlgorithmsBusinessFacade(MiscellaneousAlgorithms miscellaneo
this.miscellaneousAlgorithms = miscellaneousAlgorithms;
}
+ NodeCountResult nodeCount(Graph graph, NodeCountBaseConfig configuration) {
+ var params = configuration.toParameters();
+ var task = MiscellaneousAlgorithmsTasks.nodeCount(graph);
+ var progressTracker = progressTrackerCreator.createProgressTracker(task, configuration);
+
+ return algorithmMachinery.getResult(
+ () -> miscellaneousAlgorithms.nodeCount(graph, progressTracker),
+ progressTracker,
+ params.concurrency()
+ );
+ }
+
public SingleTypeRelationships collapsePath(GraphStore graphStore, CollapsePathConfig configuration) {
var params = CollapsePathParamsTransformer.create(configuration,graphStore);
return miscellaneousAlgorithms.collapsePath(graphStore, params);
diff --git a/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousApplicationsEstimationModeBusinessFacade.java b/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousApplicationsEstimationModeBusinessFacade.java
index 01f28135f84..9feab11c9fa 100644
--- a/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousApplicationsEstimationModeBusinessFacade.java
+++ b/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousApplicationsEstimationModeBusinessFacade.java
@@ -25,6 +25,8 @@
import org.neo4j.gds.exceptions.MemoryEstimationNotImplementedException;
import org.neo4j.gds.indexInverse.InverseRelationshipsMemoryEstimateDefinition;
import org.neo4j.gds.mem.MemoryEstimation;
+import org.neo4j.gds.nodecount.NodeCountBaseConfig;
+import org.neo4j.gds.nodecount.NodeCountMemoryEstimateDefinition;
import org.neo4j.gds.scaleproperties.ScalePropertiesBaseConfig;
import org.neo4j.gds.scaleproperties.ScalePropertiesMemoryEstimateDefinition;
import org.neo4j.gds.undirected.ToUndirectedConfig;
@@ -42,6 +44,21 @@ public MemoryEstimation collapsePath() {
throw new MemoryEstimationNotImplementedException();
}
+ public MemoryEstimation nodeCount() {
+ return new NodeCountMemoryEstimateDefinition().memoryEstimation();
+ }
+
+ public MemoryEstimateResult nodeCount(
+ NodeCountBaseConfig configuration,
+ Object graphNameOrConfiguration
+ ) {
+ return algorithmEstimationTemplate.estimate(
+ configuration,
+ graphNameOrConfiguration,
+ nodeCount()
+ );
+ }
+
public MemoryEstimation indexInverse(AlgoBaseConfig configuration) {
return new InverseRelationshipsMemoryEstimateDefinition(configuration.relationshipTypes()).memoryEstimation();
}
diff --git a/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousApplicationsStreamModeBusinessFacade.java b/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousApplicationsStreamModeBusinessFacade.java
index b6f726db3db..1332ca2e393 100644
--- a/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousApplicationsStreamModeBusinessFacade.java
+++ b/applications/algorithms/miscellaneous-algorithms/src/main/java/org/neo4j/gds/applications/algorithms/miscellaneous/MiscellaneousApplicationsStreamModeBusinessFacade.java
@@ -22,11 +22,14 @@
import org.neo4j.gds.api.GraphName;
import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTemplateConvenience;
import org.neo4j.gds.applications.algorithms.machinery.StreamResultBuilder;
+import org.neo4j.gds.nodecount.NodeCountResult;
+import org.neo4j.gds.nodecount.NodeCountStreamConfig;
import org.neo4j.gds.scaleproperties.ScalePropertiesResult;
import org.neo4j.gds.scaleproperties.ScalePropertiesStreamConfig;
import java.util.stream.Stream;
+import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.NodeCount;
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.ScaleProperties;
public class MiscellaneousApplicationsStreamModeBusinessFacade {
@@ -44,6 +47,21 @@ public class MiscellaneousApplicationsStreamModeBusinessFacade {
this.algorithmProcessingTemplateConvenience = algorithmProcessingTemplateConvenience;
}
+ public Stream nodeCount(
+ GraphName graphName,
+ NodeCountStreamConfig configuration,
+ StreamResultBuilder resultBuilder
+ ) {
+ return algorithmProcessingTemplateConvenience.processRegularAlgorithmInStreamMode(
+ graphName,
+ configuration,
+ NodeCount,
+ estimationFacade::nodeCount,
+ (graph, __) -> miscellaneousAlgorithms.nodeCount(graph, configuration),
+ resultBuilder
+ );
+ }
+
public Stream scaleProperties(
GraphName graphName,
ScalePropertiesStreamConfig configuration,
diff --git a/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java b/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java
index d451283bab5..539e1f32947 100644
--- a/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java
+++ b/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java
@@ -150,6 +150,9 @@ class OpenGdsProcedureSmokeTest extends BaseProcTest {
"gds.hits.write",
"gds.hits.write.estimate",
+ "gds.nodeCount.stream",
+ "gds.nodeCount.stream.estimate",
+
"gds.nodeSimilarity.filtered.mutate",
"gds.nodeSimilarity.filtered.mutate.estimate",
"gds.nodeSimilarity.filtered.stats",
@@ -643,7 +646,7 @@ void countShouldMatch() {
);
// If you find yourself updating this count, please also update the count in SmokeTest.kt
- int expectedCount = 482;
+ int expectedCount = 484;
assertEquals(
expectedCount,
returnedRows,
diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/LocalMiscellaneousProcedureFacade.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/LocalMiscellaneousProcedureFacade.java
index 7fe04b1e408..a68681fd696 100644
--- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/LocalMiscellaneousProcedureFacade.java
+++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/LocalMiscellaneousProcedureFacade.java
@@ -34,6 +34,7 @@
import org.neo4j.gds.procedures.algorithms.miscellaneous.stubs.LocalToUndirectedMutateStub;
import org.neo4j.gds.procedures.algorithms.miscellaneous.stubs.MiscellaneousStubs;
import org.neo4j.gds.procedures.algorithms.stubs.GenericStub;
+import org.neo4j.gds.nodecount.NodeCountStreamConfig;
import org.neo4j.gds.scaleproperties.ScalePropertiesMutateConfig;
import org.neo4j.gds.scaleproperties.ScalePropertiesStatsConfig;
import org.neo4j.gds.scaleproperties.ScalePropertiesStreamConfig;
@@ -129,6 +130,33 @@ public MiscellaneousStubs miscellaneousStubs() {
return stubs;
}
+ @Override
+ public Stream nodeCountStream(
+ String graphName,
+ Map configuration
+ ) {
+ var resultBuilder = new NodeCountResultBuilderForStreamMode();
+
+ return streamModeBusinessFacade.nodeCount(
+ GraphName.parse(graphName),
+ configurationParser.parseConfiguration(configuration, NodeCountStreamConfig::of),
+ resultBuilder
+ );
+ }
+
+ @Override
+ public Stream nodeCountStreamEstimate(
+ Object graphNameOrConfiguration,
+ Map algorithmConfiguration
+ ) {
+ var result = estimationModeBusinessFacade.nodeCount(
+ configurationParser.parseConfiguration(algorithmConfiguration, NodeCountStreamConfig::of),
+ graphNameOrConfiguration
+ );
+
+ return Stream.of(result);
+ }
+
@Override
public Stream collapsePathMutate(String graphName, Map configuration) {
return stubs.collapsePath().execute(graphName,configuration);
diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/NodeCountResultBuilderForStreamMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/NodeCountResultBuilderForStreamMode.java
new file mode 100644
index 00000000000..9ed383715e5
--- /dev/null
+++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/NodeCountResultBuilderForStreamMode.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.procedures.algorithms.miscellaneous;
+
+import org.neo4j.gds.api.Graph;
+import org.neo4j.gds.api.GraphStore;
+import org.neo4j.gds.applications.algorithms.machinery.StreamResultBuilder;
+import org.neo4j.gds.nodecount.NodeCountResult;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * Turns the algorithm result into the rows the {@code gds.nodeCount.stream} procedure yields.
+ * The count is a single scalar, so we emit exactly one row (or none on an empty graph result).
+ */
+class NodeCountResultBuilderForStreamMode implements StreamResultBuilder {
+
+ @Override
+ public Stream build(
+ Graph graph,
+ GraphStore graphStore,
+ Optional result
+ ) {
+ if (result.isEmpty()) return Stream.empty();
+
+ return Stream.of(new NodeCountStreamResult(result.get().nodeCount()));
+ }
+}
diff --git a/procedures/facade-api/configs/miscellaneous-configs/src/main/java/org/neo4j/gds/nodecount/NodeCountBaseConfig.java b/procedures/facade-api/configs/miscellaneous-configs/src/main/java/org/neo4j/gds/nodecount/NodeCountBaseConfig.java
new file mode 100644
index 00000000000..1b5f8883ab0
--- /dev/null
+++ b/procedures/facade-api/configs/miscellaneous-configs/src/main/java/org/neo4j/gds/nodecount/NodeCountBaseConfig.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+import org.neo4j.gds.annotation.Configuration;
+import org.neo4j.gds.config.AlgoBaseConfig;
+
+/**
+ * The configuration shared by every mode of the algorithm.
+ * Node counting exposes no algorithm-specific settings, so it simply inherits the common options
+ * (concurrency, node/relationship filters, ...) from {@link AlgoBaseConfig} and maps them onto the
+ * algorithm {@link NodeCountParameters parameters}. A real algorithm would declare its own settings here.
+ */
+public interface NodeCountBaseConfig extends AlgoBaseConfig {
+
+ @Configuration.Ignore
+ default NodeCountParameters toParameters() {
+ return new NodeCountParameters(concurrency());
+ }
+}
diff --git a/procedures/facade-api/configs/miscellaneous-configs/src/main/java/org/neo4j/gds/nodecount/NodeCountStreamConfig.java b/procedures/facade-api/configs/miscellaneous-configs/src/main/java/org/neo4j/gds/nodecount/NodeCountStreamConfig.java
new file mode 100644
index 00000000000..173e5750399
--- /dev/null
+++ b/procedures/facade-api/configs/miscellaneous-configs/src/main/java/org/neo4j/gds/nodecount/NodeCountStreamConfig.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+import org.neo4j.gds.annotation.Configuration;
+import org.neo4j.gds.core.CypherMapWrapper;
+
+@Configuration
+public interface NodeCountStreamConfig extends NodeCountBaseConfig {
+
+ static NodeCountStreamConfig of(CypherMapWrapper userInput) {
+ return new NodeCountStreamConfigImpl(userInput);
+ }
+}
diff --git a/procedures/facade-api/miscellaneous-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/MiscellaneousProcedureFacade.java b/procedures/facade-api/miscellaneous-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/MiscellaneousProcedureFacade.java
index a812ae721fd..f232e2c6fce 100644
--- a/procedures/facade-api/miscellaneous-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/MiscellaneousProcedureFacade.java
+++ b/procedures/facade-api/miscellaneous-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/MiscellaneousProcedureFacade.java
@@ -29,6 +29,16 @@ public interface MiscellaneousProcedureFacade {
MiscellaneousStubs miscellaneousStubs();
+ Stream nodeCountStream(
+ String graphName,
+ Map configuration
+ );
+
+ Stream nodeCountStreamEstimate(
+ Object graphNameOrConfiguration,
+ Map algorithmConfiguration
+ );
+
Stream collapsePathMutate(
String graphName,
Map configuration
diff --git a/procedures/facade-api/miscellaneous-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/NodeCountStreamResult.java b/procedures/facade-api/miscellaneous-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/NodeCountStreamResult.java
new file mode 100644
index 00000000000..5e1698880d5
--- /dev/null
+++ b/procedures/facade-api/miscellaneous-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/miscellaneous/NodeCountStreamResult.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.procedures.algorithms.miscellaneous;
+
+/**
+ * One row of the {@code gds.nodeCount.stream} procedure. Because node counting yields a single scalar,
+ * the stream produces exactly one row holding the count.
+ */
+public record NodeCountStreamResult(long nodeCount) {
+}
diff --git a/procedures/misc/src/main/java/org/neo4j/gds/nodecount/Constants.java b/procedures/misc/src/main/java/org/neo4j/gds/nodecount/Constants.java
new file mode 100644
index 00000000000..abb32851b21
--- /dev/null
+++ b/procedures/misc/src/main/java/org/neo4j/gds/nodecount/Constants.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+final class Constants {
+
+ private Constants() {}
+
+ static final String NODE_COUNT_DESCRIPTION = "Counts the number of nodes in the graph.";
+
+}
diff --git a/procedures/misc/src/main/java/org/neo4j/gds/nodecount/NodeCountStreamProc.java b/procedures/misc/src/main/java/org/neo4j/gds/nodecount/NodeCountStreamProc.java
new file mode 100644
index 00000000000..56e50e8737f
--- /dev/null
+++ b/procedures/misc/src/main/java/org/neo4j/gds/nodecount/NodeCountStreamProc.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult;
+import org.neo4j.gds.procedures.GraphDataScienceProcedures;
+import org.neo4j.gds.procedures.algorithms.miscellaneous.NodeCountStreamResult;
+import org.neo4j.procedure.Context;
+import org.neo4j.procedure.Description;
+import org.neo4j.procedure.Name;
+import org.neo4j.procedure.Procedure;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.neo4j.gds.nodecount.Constants.NODE_COUNT_DESCRIPTION;
+import static org.neo4j.gds.procedures.ProcedureConstants.MEMORY_ESTIMATION_DESCRIPTION;
+import static org.neo4j.procedure.Mode.READ;
+
+/**
+ * The thin Neo4j procedure layer: it only injects the {@link GraphDataScienceProcedures} context object and
+ * delegates to the procedure facade. Neo4j discovers this class by scanning the packaged plugin jar for
+ * {@link Procedure}-annotated methods, so no explicit registration is needed.
+ */
+public class NodeCountStreamProc {
+ @Context
+ public GraphDataScienceProcedures facade;
+
+ @Procedure(value = "gds.nodeCount.stream", mode = READ)
+ @Description(NODE_COUNT_DESCRIPTION)
+ public Stream stream(
+ @Name(value = "graphName") String graphName,
+ @Name(value = "configuration", defaultValue = "{}") Map configuration
+ ) {
+ return facade.algorithms().miscellaneous().nodeCountStream(graphName, configuration);
+ }
+
+ @Procedure(value = "gds.nodeCount.stream.estimate", mode = READ)
+ @Description(MEMORY_ESTIMATION_DESCRIPTION)
+ public Stream estimate(
+ @Name(value = "graphNameOrConfiguration") Object graphName,
+ @Name(value = "algoConfiguration") Map configuration
+ ) {
+ return facade.algorithms().miscellaneous().nodeCountStreamEstimate(graphName, configuration);
+ }
+}
diff --git a/procedures/misc/src/test/java/org/neo4j/gds/nodecount/NodeCountStreamProcTest.java b/procedures/misc/src/test/java/org/neo4j/gds/nodecount/NodeCountStreamProcTest.java
new file mode 100644
index 00000000000..60baad34058
--- /dev/null
+++ b/procedures/misc/src/test/java/org/neo4j/gds/nodecount/NodeCountStreamProcTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.gds.nodecount;
+
+import org.intellij.lang.annotations.Language;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.neo4j.gds.BaseProcTest;
+import org.neo4j.gds.GdsCypher;
+import org.neo4j.gds.catalog.GraphProjectProc;
+import org.neo4j.gds.extension.Neo4jGraph;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.isA;
+
+class NodeCountStreamProcTest extends BaseProcTest {
+
+ @Neo4jGraph
+ @Language("Cypher")
+ private static final String DB_CYPHER =
+ "CREATE" +
+ " (n0:A)" +
+ ",(n1:A)" +
+ ",(n2:A)";
+
+ @BeforeEach
+ void setUp() throws Exception {
+ registerProcedures(
+ GraphProjectProc.class,
+ NodeCountStreamProc.class
+ );
+
+ runQuery("CALL gds.graph.project('g', 'A', '*')");
+ }
+
+ @Test
+ void stream() {
+ var query = GdsCypher
+ .call("g")
+ .algo("gds.nodeCount")
+ .streamMode()
+ .yields();
+
+ assertCypherResult(query, List.of(Map.of("nodeCount", 3L)));
+ }
+
+ @Test
+ void estimate() {
+ var query = GdsCypher
+ .call("g")
+ .algo("gds.nodeCount")
+ .streamEstimation()
+ .yields();
+
+ assertCypherResult(query, List.of(Map.of(
+ "mapView", isA(Map.class),
+ "treeView", isA(String.class),
+ "bytesMax", greaterThanOrEqualTo(0L),
+ "heapPercentageMin", greaterThanOrEqualTo(0.0),
+ "nodeCount", 3L,
+ "relationshipCount", 0L,
+ "requiredMemory", isA(String.class),
+ "bytesMin", greaterThanOrEqualTo(0L),
+ "heapPercentageMax", greaterThanOrEqualTo(0.0)
+ ))
+ );
+ }
+}