-
Notifications
You must be signed in to change notification settings - Fork 493
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added support for Duration to apoc.coll.avg (#2987)
* added coll avg duration * moved into full * added coll avg duration * changed implementation - added tests * small adoc change
- Loading branch information
Showing
13 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
docs/asciidoc/modules/ROOT/examples/generated-documentation/apoc.coll.avgDuration-lite.csv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
¦signature | ||
¦apoc.coll.avgDuration(durations :: LIST? OF DURATION?) :: (DURATION?) |
5 changes: 5 additions & 0 deletions
5
...ciidoc/modules/ROOT/examples/generated-documentation/apoc.coll.avgDuration.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
¦xref::overview/apoc.coll/apoc.coll.avgDuration.adoc[apoc.coll.avgDuration icon:book[]] + | ||
|
||
`apoc.coll.avgDuration([duration('P2DT3H'), duration('PT1H45S'), ...])` - returns the average of a list of duration values | ||
¦label:function[] | ||
¦label:apoc-full[] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
docs/asciidoc/modules/ROOT/pages/overview/apoc.coll/apoc.coll.avgDuration.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
//// | ||
This file is generated by DocsTest, so don't change it! | ||
//// | ||
|
||
= apoc.coll.avgDuration | ||
:description: This section contains reference documentation for the apoc.coll.avgDuration function. | ||
|
||
label:function[] label:apoc-full[] | ||
|
||
[.emphasis] | ||
apoc.coll.avgDuration([duration('P2DT3H'), duration('PT1H45S'), ...]) - returns the average of a list of duration values | ||
|
||
== Signature | ||
|
||
[source] | ||
---- | ||
apoc.coll.avgDuration(durations :: LIST? OF DURATION?) :: (DURATION?) | ||
---- | ||
|
||
== Input parameters | ||
[.procedures, opts=header] | ||
|=== | ||
| Name | Type | Default | ||
|durations|LIST? OF DURATION?|null | ||
|=== | ||
|
||
[[usage-apoc.coll.avgDuration]] | ||
== Usage Examples | ||
include::partial$usage/apoc.coll.avgDuration.adoc[] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
docs/asciidoc/modules/ROOT/partials/usage/apoc.coll.avgDuration.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
The `apoc.coll.avgDuration` works similar to the `avg()` function, | ||
but it's not an aggregate function and takes a list of durations as an argument. | ||
For example: | ||
|
||
[source,cypher] | ||
---- | ||
WITH [duration('P2DT4H1S'), duration('PT1H1S'), duration('PT1H6S'), duration('PT1H5S')] AS durations | ||
RETURN apoc.coll.avgDuration(durations) AS value | ||
---- | ||
|
||
.Results | ||
[opts="header"] | ||
|=== | ||
| value | ||
| PT13H45M3.25S | ||
|=== | ||
|
||
|
||
In case of null or empty list, a `null` result will be returned: | ||
[source,cypher] | ||
---- | ||
RETURN apoc.coll.avgDuration([]) AS output; | ||
---- | ||
|
||
[source,cypher] | ||
---- | ||
RETURN apoc.coll.avgDuration(null) AS output; | ||
---- | ||
|
||
.Results | ||
[opts="header"] | ||
|=== | ||
| output | ||
| null | ||
|=== | ||
|
||
In case a non-duration list is passed, a `Type mismatch` error will be thrown: | ||
[source,cypher] | ||
---- | ||
RETURN apoc.coll.avgDuration([1,2,3]) AS value; | ||
---- | ||
|
||
.Results | ||
|=== | ||
| Type mismatch: expected List<Duration> but was List<Integer> | ||
|=== | ||
|
||
|
||
While in case a list with all duration values is not passed, a `TypeError` will be thrown: | ||
|
||
[source,cypher] | ||
---- | ||
RETURN apoc.coll.avgDuration([duration('PT1H1S'),2,3]) AS output; | ||
---- | ||
|
||
.Results | ||
|=== | ||
| Can't coerce `Long(2)` to Duration | ||
|=== |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package apoc.coll; | ||
|
||
import apoc.Extended; | ||
import org.apache.commons.collections4.CollectionUtils; | ||
import org.neo4j.procedure.Description; | ||
import org.neo4j.procedure.Name; | ||
import org.neo4j.procedure.UserFunction; | ||
import org.neo4j.values.storable.DurationValue; | ||
|
||
import java.time.temporal.ChronoUnit; | ||
import java.util.List; | ||
|
||
@Extended | ||
public class CollFull { | ||
|
||
@UserFunction | ||
@Description("apoc.coll.avgDuration([duration('P2DT3H'), duration('PT1H45S'), ...]) - returns the average of a list of duration values") | ||
public DurationValue avgDuration(@Name("durations") List<DurationValue> list) { | ||
if (CollectionUtils.isEmpty(list)) return null; | ||
|
||
long count = 0; | ||
|
||
double monthsRunningAvg = 0; | ||
double daysRunningAvg = 0; | ||
double secondsRunningAvg = 0; | ||
double nanosRunningAvg = 0; | ||
for (DurationValue duration : list) { | ||
count++; | ||
monthsRunningAvg += (duration.get(ChronoUnit.MONTHS) - monthsRunningAvg) / count; | ||
daysRunningAvg += (duration.get(ChronoUnit.DAYS) - daysRunningAvg) / count; | ||
secondsRunningAvg += (duration.get(ChronoUnit.SECONDS) - secondsRunningAvg) / count; | ||
nanosRunningAvg += (duration.get(ChronoUnit.NANOS) - nanosRunningAvg) / count; | ||
} | ||
|
||
return DurationValue.approximate(monthsRunningAvg, daysRunningAvg, secondsRunningAvg, nanosRunningAvg) | ||
.normalize(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package apoc.coll; | ||
|
||
import apoc.util.TestUtil; | ||
import org.junit.BeforeClass; | ||
import org.junit.ClassRule; | ||
import org.junit.Test; | ||
import org.neo4j.test.rule.DbmsRule; | ||
import org.neo4j.test.rule.ImpermanentDbmsRule; | ||
import org.neo4j.values.storable.DurationValue; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import static apoc.util.TestUtil.testCall; | ||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertNull; | ||
import static org.junit.Assert.fail; | ||
|
||
public class CollFullTest { | ||
|
||
@ClassRule | ||
public static DbmsRule db = new ImpermanentDbmsRule(); | ||
|
||
@BeforeClass | ||
public static void setUp() throws Exception { | ||
TestUtil.registerProcedure(db, CollFull.class); | ||
} | ||
|
||
@Test | ||
public void testAvgDuration() { | ||
final List<DurationValue> list = List.of( | ||
DurationValue.parse("P2DT4H1S"), DurationValue.parse("PT1H1S"), DurationValue.parse("PT1H6S"), DurationValue.parse("PT1H5S")); | ||
|
||
// get duration from Neo4j aggregation AvgFunction | ||
final DurationValue expected = TestUtil.singleResultFirstColumn(db, "UNWIND $list AS dur RETURN avg(dur) AS value", | ||
Map.of("list", list)); | ||
|
||
// same duration values as above | ||
testCall(db, "WITH $list AS dur RETURN apoc.coll.avgDuration(dur) AS value", | ||
Map.of("list", list), | ||
(row) -> assertEquals(expected, row.get("value"))); | ||
} | ||
|
||
@Test | ||
public void testAvgDurationNullOrEmpty() { | ||
testCall(db, "WITH [] AS dur " + | ||
"RETURN apoc.coll.avgDuration(dur) AS value", | ||
(row) -> assertNull(row.get("value"))); | ||
|
||
testCall(db, "WITH null AS dur " + | ||
"RETURN apoc.coll.avgDuration(dur) AS value", | ||
(row) -> assertNull(row.get("value"))); | ||
|
||
} | ||
|
||
@Test | ||
public void testAvgDurationWrongType() { | ||
final String queryIntType = "WITH [1,2,3] AS dur " + | ||
"RETURN apoc.coll.avgDuration(dur)"; | ||
testWrongType(queryIntType); | ||
|
||
final String queryMixedType = "WITH [duration('P2DT4H1S'), duration('PT1H6S'), 1] AS dur " + | ||
"RETURN apoc.coll.avgDuration(dur)"; | ||
testWrongType(queryMixedType); | ||
} | ||
|
||
private void testWrongType(String query) { | ||
try { | ||
testCall(db, query, row -> fail("should fail due to Wrong argument type")); | ||
} catch (RuntimeException e) { | ||
assertEquals("Wrong argument type: Can't coerce `Long(1)` to Duration", e.getMessage()); | ||
} | ||
} | ||
} |