Skip to content

Commit

Permalink
Fixes #577: return apoc.nodes.count(['User']) (#2277)
Browse files Browse the repository at this point in the history
  • Loading branch information
vga91 authored and JMHReif committed Jan 13, 2022
1 parent 36642a1 commit 3278949
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 1 deletion.
47 changes: 47 additions & 0 deletions core/src/main/java/apoc/meta/Meta.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom;
Expand Down Expand Up @@ -372,6 +373,52 @@ public Stream<MetaStats> stats() {
return Stream.of(collectStats());
}

@UserFunction(name = "apoc.meta.nodes.count")
@Description("apoc.meta.nodes.count([labels], $config) - Returns the sum of the nodes with a label present in the list.")
public long count(@Name(value = "nodes", defaultValue = "[]") List<String> nodes, @Name(value = "config", defaultValue = "{}") Map<String, Object> config) {
MetaConfig conf = new MetaConfig(config);
final DatabaseSubGraph subGraph = new DatabaseSubGraph(transaction);
Stream<Label> labels = CollectionUtils.isEmpty(nodes)
? StreamSupport.stream(subGraph.getAllLabelsInUse().spliterator(), false)
: nodes.stream().filter(Objects::nonNull).map(String::trim).map(Label::label);

final boolean isIncludeRels = CollectionUtils.isEmpty(conf.getIncludesRels());
Set<Long> visitedNodes = new HashSet<>();
return labels
.flatMap(label -> isIncludeRels ? Stream.of(subGraph.countsForNode(label)) : conf.getIncludesRels()
.stream()
.filter(Objects::nonNull)
.map(String::trim)
.map(rel -> {
final int lastCharIdx = rel.length() - 1;
final Direction direction;
switch (rel.charAt(lastCharIdx)) {
case '>':
direction = Direction.OUTGOING;
rel = rel.substring(0, lastCharIdx);
break;
case '<':
direction = Direction.INCOMING;
rel = rel.substring(0, lastCharIdx);
break;
default:
direction = Direction.BOTH;
}
return Pair.of(direction, rel);
})
.flatMap(pair -> transaction.findNodes(label)
.map(node -> {
if (!visitedNodes.contains(node.getId()) && node.hasRelationship(pair.first(), RelationshipType.withName(pair.other()))) {
visitedNodes.add(node.getId());
return 1L;
} else {
return 0L;
}
})
.stream()))
.reduce(0L, Math::addExact);
}

private MetaStats collectStats() {
Map<String, Long> relStatsCount = new LinkedHashMap<>();
TokenRead tokenRead = kernelTx.tokenRead();
Expand Down
46 changes: 45 additions & 1 deletion core/src/test/java/apoc/meta/MetaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1407,5 +1407,49 @@ public void testMetaStatsWithLabelAndRelTypeCountInUse() {
assertEquals(expectedRelTypes, row.get("relTypes"));
});
}


@Test
public void testMetaNodesCount() {
db.executeTransactionally("CREATE (:MyCountLabel {id: 1}), (:MyCountLabel {id: 2}), (:ThirdLabel {id: 3})");

// 2 outcome rels and 1 incoming
db.executeTransactionally("MATCH (n:MyCountLabel {id: 1}), (m:ThirdLabel {id: 3}) " +
"CREATE (n)-[:MY_COUNT_REL]->(m), (n)-[:ANOTHER_MY_COUNT_REL]->(m), (n)<-[:ANOTHER_MY_COUNT_REL]-(m)");

TestUtil.testCall(db, "RETURN apoc.meta.nodes.count(['MyCountLabel'], {rels: ['MY_COUNT_REL']}) AS count",
row -> assertEquals(1L, row.get("count")));

TestUtil.testCall(db, "RETURN apoc.meta.nodes.count(['MyCountLabel', 'NotExistent'], {rels: ['MY_COUNT_REL']}) AS count",
row -> assertEquals(1L, row.get("count")));

TestUtil.testCall(db, "RETURN apoc.meta.nodes.count(['MyCountLabel'], {rels: ['MY_COUNT_REL>']}) AS count",
row -> assertEquals(1L, row.get("count")));

TestUtil.testCall(db, "RETURN apoc.meta.nodes.count(['MyCountLabel'], {rels: ['MY_COUNT_REL<']}) AS count",
row -> assertEquals(0L, row.get("count")));

TestUtil.testCall(db, "RETURN apoc.meta.nodes.count(['MyCountLabel'], {rels: ['MY_COUNT_REL', 'ANOTHER_MY_COUNT_REL']}) AS count",
row -> assertEquals(1L, row.get("count")));

// another 2 nodes with 2 new labels
db.executeTransactionally("CREATE (:AnotherCountLabel)<-[:MY_COUNT_REL]-(:NotInCountLabel)");

TestUtil.testCall(db, "RETURN apoc.meta.nodes.count(['MyCountLabel', 'AnotherCountLabel'], {rels: ['MY_COUNT_REL', 'ANOTHER_MY_COUNT_REL']}) AS count",
row -> assertEquals(2L, row.get("count")));

// create another 2 rels in `MyCountLabel` nodes
db.executeTransactionally("MATCH (n:MyCountLabel) WITH n CREATE (n)<-[:MY_COUNT_REL]-(:NotInCountLabel)");

TestUtil.testCall(db, "RETURN apoc.meta.nodes.count(['MyCountLabel', 'AnotherCountLabel'], {rels: ['MY_COUNT_REL', 'ANOTHER_MY_COUNT_REL']}) AS count",
row -> assertEquals(3L, row.get("count")));

TestUtil.testCall(db, "RETURN apoc.meta.nodes.count(['MyCountLabel', 'AnotherCountLabel'], {rels: ['MY_COUNT_REL', 'ANOTHER_MY_COUNT_REL']}) AS count",
row -> assertEquals(3L, row.get("count")));

// just to check that with both direction takes all
TestUtil.testCall(db, "RETURN apoc.meta.nodes.count(['MyCountLabel', 'AnotherCountLabel'], {rels: ['MY_COUNT_REL>', 'MY_COUNT_REL<', 'ANOTHER_MY_COUNT_REL']}) AS count",
row -> assertEquals(3L, row.get("count")));

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
¦signature
¦apoc.meta.nodes.count(nodes = [] :: LIST? OF STRING?, config = {} :: MAP?) :: (INTEGER?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
¦signature
¦apoc.meta.nodes.count(nodes = [] :: LIST? OF STRING?, config = {} :: MAP?) :: (INTEGER?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
¦xref::overview/apoc.meta/apoc.meta.nodes.count.adoc[apoc.meta.nodes.count icon:book[]] +

- Returns the sum of the nodes with a label present in the list.
¦label:function[]
¦label:apoc-core[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
¦Qualified Name¦Type¦Release
|xref::overview/apoc.meta.nodes/apoc.meta.adoc[apoc.meta.nodes.count icon:book[]]

apoc.meta.nodes.count([labels], $config) - Returns the sum of the nodes with a label present in the list.
|label:function[]
|label:apoc-core[]
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ for the provided `label` and `uuidProperty`, in case the UUID handler is already
¦function¦apoc.meta.cypher.type¦apoc.meta.cypher.type(value :: ANY?) :: (STRING?)¦apoc.meta.cypher.type(value) - type name of a value (INTEGER,FLOAT,STRING,BOOLEAN,RELATIONSHIP,NODE,PATH,NULL,MAP,LIST OF <TYPE>,POINT,DATE,DATE_TIME,LOCAL_TIME,LOCAL_DATE_TIME,TIME,DURATION)¦true¦
¦function¦apoc.meta.cypher.types¦apoc.meta.cypher.types(properties :: ANY?) :: (MAP?)¦apoc.meta.cypher.types(node-relationship-map) - returns a map of keys to types¦true¦
¦function¦apoc.meta.isType¦apoc.meta.isType(value :: ANY?, type :: STRING?) :: (BOOLEAN?)¦apoc.meta.isType(value,type) - returns a row if type name matches none if not (INTEGER,FLOAT,STRING,BOOLEAN,RELATIONSHIP,NODE,PATH,NULL,UNKNOWN,MAP,LIST)¦true¦
¦function¦apoc.meta.nodes.count¦apoc.meta.nodes.count(nodes = [] :: LIST? OF STRING?, config = {} :: MAP?) :: (INTEGER?)¦apoc.meta.nodes.count([labels], $config) - Returns the sum of the nodes with a label present in the list.¦true¦
¦function¦apoc.meta.type¦apoc.meta.type(value :: ANY?) :: (STRING?)¦apoc.meta.type(value) - type name of a value (INTEGER,FLOAT,STRING,BOOLEAN,RELATIONSHIP,NODE,PATH,NULL,UNKNOWN,MAP,LIST)¦true¦
¦function¦apoc.meta.typeName¦apoc.meta.typeName(value :: ANY?) :: (STRING?)¦apoc.meta.typeName(value) - type name of a value (INTEGER,FLOAT,STRING,BOOLEAN,RELATIONSHIP,NODE,PATH,NULL,UNKNOWN,MAP,LIST)¦true¦
¦function¦apoc.meta.types¦apoc.meta.types(properties :: ANY?) :: (MAP?)¦apoc.meta.types(node-relationship-map) - returns a map of keys to types¦true¦
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
////
This file is generated by DocsTest, so don't change it!
////

= apoc.meta.nodes.count
:description: This section contains reference documentation for the apoc.meta.nodes.count function.

label:function[] label:apoc-core[]

[.emphasis]
apoc.meta.nodes.count([labels], $config) - Returns the sum of the nodes with a label present in the list.

== Signature

[source]
----
apoc.meta.nodes.count(nodes = [] :: LIST? OF STRING?, config = {} :: MAP?) :: (INTEGER?)
----

== Input parameters
[.procedures, opts=header]
|===
| Name | Type | Default
|nodes|LIST? OF STRING?|[]
|config|MAP?|{}
|===

== Config parameters
include::partial$usage/config/apoc.meta.nodes.count.adoc[]

[[usage-apoc.meta.nodes.count]]
== Usage Examples
include::partial$usage/apoc.meta.nodes.count.adoc[]

Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ apoc.meta.cypher.types(node-relationship-map) - returns a map of keys to types
apoc.meta.isType(value,type) - returns a row if type name matches none if not (INTEGER,FLOAT,STRING,BOOLEAN,RELATIONSHIP,NODE,PATH,NULL,UNKNOWN,MAP,LIST)
|label:function[]
|label:apoc-core[]
|xref::overview/apoc.meta/apoc.meta.nodes.count.adoc[apoc.meta.nodes.count icon:book[]]

apoc.meta.nodes.count([labels], $config) - Returns the sum of the nodes with a label present in the list.
|label:function[]
|label:apoc-core[]
|xref::overview/apoc.meta/apoc.meta.type.adoc[apoc.meta.type icon:book[]]

apoc.meta.type(value) - type name of a value (INTEGER,FLOAT,STRING,BOOLEAN,RELATIONSHIP,NODE,PATH,NULL,UNKNOWN,MAP,LIST)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2002,6 +2002,11 @@ apoc.meta.cypher.types(node-relationship-map) - returns a map of keys to types
apoc.meta.isType(value,type) - returns a row if type name matches none if not (INTEGER,FLOAT,STRING,BOOLEAN,RELATIONSHIP,NODE,PATH,NULL,UNKNOWN,MAP,LIST)
|label:function[]
|label:apoc-core[]
|xref::overview/apoc.meta/apoc.meta.nodes.count.adoc[apoc.meta.nodes.count icon:book[]]

apoc.meta.nodes.count([labels], $config) - Returns the sum of the nodes with a label present in the list.
|label:function[]
|label:apoc-core[]
|xref::overview/apoc.meta/apoc.meta.type.adoc[apoc.meta.type icon:book[]]

apoc.meta.type(value) - type name of a value (INTEGER,FLOAT,STRING,BOOLEAN,RELATIONSHIP,NODE,PATH,NULL,UNKNOWN,MAP,LIST)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ This file is generated by DocsTest, so don't change it!
*** xref::overview/apoc.meta/apoc.meta.cypher.type.adoc[]
*** xref::overview/apoc.meta/apoc.meta.cypher.types.adoc[]
*** xref::overview/apoc.meta/apoc.meta.isType.adoc[]
*** xref::overview/apoc.meta/apoc.meta.nodes.count.adoc[]
*** xref::overview/apoc.meta/apoc.meta.type.adoc[]
*** xref::overview/apoc.meta/apoc.meta.typeName.adoc[]
*** xref::overview/apoc.meta/apoc.meta.types.adoc[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
The examples in this section are based on the following sample graph:

[source,cypher]
----
CREATE (n:MyCountLabel {id: 1}), (:MyCountLabel {id: 2}), (m:ThirdLabel {id: 3})
WITH n,m
CREATE (n)-[:MY_COUNT_REL]->(m), (n)-[:ANOTHER_MY_COUNT_REL]->(m), (n)<-[:ANOTHER_MY_COUNT_REL]-(m)
----

We can return all nodes with a label `MyCountLabel` or a label `ThirdLabel`

[source,cypher]
----
RETURN apoc.meta.nodes.count(['MyCountLabel', 'ThirdLabel']) AS count;
----

.Results
[opts="header"]
|===
| count | relationships
| 3
|===


We can return all nodes with a label `MyCountLabel` and a relationship `MY_COUNT_REL` through the config param `rel`

[source,cypher]
----
RETURN apoc.meta.nodes.count(['MyCountLabel'], {rels: ['MY_COUNT_REL']}) AS count;
----

.Results
[opts="header"]
|===
| count | relationships
| 1
|===

Moreover, we can return all nodes with a `outcome` relationship `MY_COUNT_REL` (with the suffix `>`):

[source,cypher]
----
RETURN apoc.meta.nodes.count(['MyCountLabel'], {rels: ['MY_COUNT_REL>']}) AS count;
----

.Results
[opts="header"]
|===
| count | relationships
| 1
|===

otherwise with an `incoming` relationship `MY_COUNT_REL` (with the suffix `<`):

[source,cypher]
----
RETURN apoc.meta.nodes.count(['MyCountLabel'], {rels: ['MY_COUNT_REL<']}) AS count;
----

.Results
[opts="header"]
|===
| count | relationships
| 0
|===

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The procedure support the following config parameters:

.Config parameters
[opts=header, cols="1,1,1,5"]
|===
| name | type | default | description
| rels | Set<String> | `EmptySet` | The rel types to consider in the count.
We can add to the suffix `>` or `<` to the rel type name to indicate an outgoing or incoming relationship.
|===

0 comments on commit 3278949

Please sign in to comment.