-
Notifications
You must be signed in to change notification settings - Fork 495
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #1464: Create apoc.temporal.overlap(start1, end1, start2, end2,…
… acceptAdjacentSpans:true) function
- Loading branch information
Showing
7 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
43 changes: 43 additions & 0 deletions
43
docs/asciidoc/modules/ROOT/pages/overview/apoc.temporal/apoc.temporal.overlap.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,43 @@ | ||
= apoc.temporal.overlap | ||
:description: This section contains reference documentation for the apoc.temporal.overlap function. | ||
|
||
label:function[] label:apoc-extended[] | ||
|
||
[.emphasis] | ||
apoc.temporal.overlap(start1,end1,start2,end2,$config) - Check whether the two temporal spans (start1-end1 and start2-end2) overlap or not | ||
|
||
== Signature | ||
|
||
[source] | ||
---- | ||
apoc.temporal.overlap(start1 :: ANY?, end1 :: ANY?, start2 :: ANY?, end2 :: ANY?, config = {} :: MAP?) :: BOOLEAN? | ||
---- | ||
|
||
== Input parameters | ||
[.procedures, opts=header] | ||
|=== | ||
| Name | Type | Default | ||
|start1|ANY?|null | ||
|end1|ANY?|null | ||
|start2|ANY?|null | ||
|end2|ANY?|null | ||
|config|MAP?|{} | ||
|=== | ||
|
||
|
||
== Config parameters | ||
|
||
The function support the following config parameters: | ||
|
||
.Config parameters | ||
[opts=header] | ||
|=== | ||
| name | type | default | description | ||
| acceptAdjacentSpans | boolean | false | also considers adjacent spans | ||
|=== | ||
|
||
[[usage-apoc.temporal.overlap]] | ||
== Usage Examples | ||
include::partial$usage/apoc.temporal.overlap.adoc[] | ||
|
||
|
10 changes: 10 additions & 0 deletions
10
docs/asciidoc/modules/ROOT/pages/overview/apoc.temporal/index.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,10 @@ | ||
= apoc.temporal | ||
:description: This section contains reference documentation for the apoc.temporal procedures. | ||
|
||
[.procedures, opts=header, cols='5a,1a'] | ||
|=== | ||
| Qualified Name | Type | ||
|xref::overview/apoc.temporal/apoc.temporal.overlap.adoc[apoc.temporal.overlap icon:book[]] | ||
|
||
|label:function[] | ||
|=== |
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
74 changes: 74 additions & 0 deletions
74
docs/asciidoc/modules/ROOT/partials/usage/apoc.temporal.overlap.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,74 @@ | ||
.adjacent spans with default config | ||
[source,cypher] | ||
---- | ||
RETURN apoc.temporal.overlap( | ||
date("1999"), | ||
date("2000"), | ||
date("2000"), | ||
date("2001")) | ||
AS value | ||
---- | ||
|
||
.Results | ||
[opts="header"] | ||
|=== | ||
| value | ||
| false | ||
|=== | ||
|
||
|
||
.adjacent spans with config acceptAdjacentSpans: true | ||
[source,cypher] | ||
---- | ||
RETURN apoc.temporal.overlap( | ||
date("1999"), | ||
date("2000"), | ||
date("2000"), | ||
date("2001"), | ||
{acceptAdjacentSpans: true} ) | ||
AS value | ||
---- | ||
|
||
.Results | ||
[opts="header"] | ||
|=== | ||
| value | ||
| true | ||
|=== | ||
|
||
.duration spans | ||
[source,cypher] | ||
---- | ||
RETURN apoc.temporal.overlap( | ||
time("00:01"), | ||
time("01:01"), | ||
time("00:00"), | ||
time("00:02") ) | ||
AS value | ||
---- | ||
|
||
.Results | ||
[opts="header"] | ||
|=== | ||
| value | ||
| true | ||
|=== | ||
|
||
|
||
.non-comparable spans | ||
[source,cypher] | ||
---- | ||
RETURN apoc.temporal.overlap( | ||
date("1998"), | ||
date("1999"), | ||
time("00:00"), | ||
time("00:02") ) | ||
AS value | ||
---- | ||
|
||
.Results | ||
[opts="header"] | ||
|=== | ||
| value | ||
| null | ||
|=== |
44 changes: 44 additions & 0 deletions
44
extended/src/main/java/apoc/temporal/TemporalExtended.java
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,44 @@ | ||
package apoc.temporal; | ||
|
||
import apoc.Extended; | ||
import apoc.util.Util; | ||
import org.neo4j.graphdb.Result; | ||
import org.neo4j.graphdb.Transaction; | ||
import org.neo4j.procedure.Context; | ||
import org.neo4j.procedure.Description; | ||
import org.neo4j.procedure.Name; | ||
import org.neo4j.procedure.UserFunction; | ||
|
||
import java.util.Map; | ||
|
||
@Extended | ||
public class TemporalExtended { | ||
public static final String ACCEPT_ADJACENT_KEY = "acceptAdjacentSpans"; | ||
|
||
@Context | ||
public Transaction tx; | ||
|
||
@UserFunction("apoc.temporal.overlap") | ||
@Description("apoc.temporal.overlap(start1,end1,start2,end2,$config) - Check whether the two temporal spans (start1-end1 and start2-end2) overlap or not") | ||
public Boolean overlap(@Name("start1") Object start1, | ||
@Name("end1") Object end1, | ||
@Name("start2") Object start2, | ||
@Name("end2") Object end2, | ||
@Name(value = "config", defaultValue = "{}") Map<String, Object> config) { | ||
|
||
boolean acceptAdjacentSpans = Util.toBoolean(config.get(ACCEPT_ADJACENT_KEY)); | ||
|
||
String operator = acceptAdjacentSpans ? "<=" : "<"; | ||
String query = "RETURN ($start1 %1$s $end2) AND ($start2 %1$s $end1) AS value".formatted(operator); | ||
Map<String, Object> params = Map.of("start1", start1, | ||
"end1", end1, | ||
"start2", start2, | ||
"end2", end2); | ||
|
||
try (Result result = tx.execute(query, params)) { | ||
Object value = result.next().get("value"); | ||
return (Boolean) value; | ||
} | ||
} | ||
|
||
} |
139 changes: 139 additions & 0 deletions
139
extended/src/test/java/apoc/temporal/TemporalExtendedTest.java
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,139 @@ | ||
package apoc.temporal; | ||
|
||
import apoc.Extended; | ||
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.DateTimeValue; | ||
import org.neo4j.values.storable.DateValue; | ||
import org.neo4j.values.storable.LocalDateTimeValue; | ||
import org.neo4j.values.storable.LocalTimeValue; | ||
import org.neo4j.values.storable.TimeValue; | ||
|
||
import java.time.ZoneId; | ||
import java.util.Map; | ||
|
||
import static apoc.temporal.TemporalExtended.ACCEPT_ADJACENT_KEY; | ||
import static apoc.util.TestUtil.singleResultFirstColumn; | ||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertNull; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
@Extended | ||
public class TemporalExtendedTest { | ||
|
||
public static final String RETURN_OVERLAP = "RETURN apoc.temporal.overlap($start1, $end1, $start2, $end2, $conf)"; | ||
|
||
@ClassRule | ||
public static DbmsRule db = new ImpermanentDbmsRule(); | ||
|
||
@BeforeClass | ||
public static void setUp() { | ||
TestUtil.registerProcedure(db, TemporalExtended.class); | ||
} | ||
|
||
@Test | ||
public void testOverlapDates() { | ||
Map<String, Object> params = Map.of("start1", DateValue.parse("1999"), | ||
"end1", DateValue.parse("2000"), | ||
"start2", DateValue.parse("2000"), | ||
"end2", DateValue.parse("2001"), | ||
"conf", Map.of()); | ||
|
||
boolean output = singleResultFirstColumn(db, RETURN_OVERLAP, params); | ||
assertFalse(output); | ||
} | ||
|
||
@Test | ||
public void testOverlapDatesWithConfigAcceptAdjacentSpansConf() { | ||
Map<String, Object> params = Map.of("start1", DateValue.parse("1999"), | ||
"end1", DateValue.parse("2000"), | ||
"start2", DateValue.parse("2000"), | ||
"end2", DateValue.parse("2001"), | ||
"conf", Map.of(ACCEPT_ADJACENT_KEY, true)); | ||
|
||
boolean output = singleResultFirstColumn(db, RETURN_OVERLAP, params); | ||
assertTrue(output); | ||
} | ||
|
||
@Test | ||
public void testOverlapWithDatetime() { | ||
Map<String, Object> params = Map.of("start1", DateTimeValue.parse("1999", ZoneId::systemDefault), | ||
"end1", DateTimeValue.parse("2000", ZoneId::systemDefault), | ||
"start2", DateTimeValue.parse("2000", ZoneId::systemDefault), | ||
"end2", DateTimeValue.parse("2001", ZoneId::systemDefault), | ||
"conf", Map.of()); | ||
|
||
boolean output = singleResultFirstColumn(db, RETURN_OVERLAP, params); | ||
assertFalse(output); | ||
} | ||
|
||
@Test | ||
public void testOverlapWithDatetimeAndAcceptAdjacentSpansConf() { | ||
Map<String, Object> params = Map.of("start1", DateTimeValue.parse("1999", ZoneId::systemDefault), | ||
"end1", DateTimeValue.parse("2000", ZoneId::systemDefault), | ||
"start2", DateTimeValue.parse("2000", ZoneId::systemDefault), | ||
"end2", DateTimeValue.parse("2001", ZoneId::systemDefault), | ||
"conf", Map.of(ACCEPT_ADJACENT_KEY, true)); | ||
|
||
boolean output = singleResultFirstColumn(db, RETURN_OVERLAP, params); | ||
assertTrue(output); | ||
} | ||
|
||
@Test | ||
public void testOverlapWithTime() { | ||
Map<String, Object> params = Map.of("start1", TimeValue.parse("00:01", ZoneId::systemDefault), | ||
"end1", TimeValue.parse("01:01", ZoneId::systemDefault), | ||
"start2", TimeValue.parse("00:00", ZoneId::systemDefault), | ||
"end2", TimeValue.parse("00:02", ZoneId::systemDefault), | ||
"conf", Map.of()); | ||
|
||
boolean output = singleResultFirstColumn(db, RETURN_OVERLAP, params); | ||
assertTrue(output); | ||
} | ||
|
||
@Test | ||
public void testOverlapWithLocalTime() { | ||
Map<String, Object> params = Map.of("start1", LocalTimeValue.parse("00:01"), | ||
"end1", LocalTimeValue.parse("01:01"), | ||
"start2", LocalTimeValue.parse("00:00"), | ||
"end2", LocalTimeValue.parse("00:02"), | ||
"conf", Map.of(ACCEPT_ADJACENT_KEY, true)); | ||
|
||
boolean output = singleResultFirstColumn(db, RETURN_OVERLAP, params); | ||
assertTrue(output); | ||
} | ||
|
||
@Test | ||
public void testOverlapWithLocalDateTime() { | ||
Map<String, Object> params = Map.of("start1", LocalDateTimeValue.parse("1999"), | ||
"end1", LocalDateTimeValue.parse("2000"), | ||
"start2", LocalDateTimeValue.parse("2000"), | ||
"end2", LocalDateTimeValue.parse("2001"), | ||
"conf", Map.of()); | ||
|
||
boolean output = singleResultFirstColumn(db, RETURN_OVERLAP, params); | ||
assertFalse(output); | ||
} | ||
|
||
/** | ||
* In this test case the 2 ranges have different types (i.e. `date` and `time`), | ||
* and we just return `null`, | ||
* to be consistent with Cypher's behavior (e.g., `return date("1999") > time("19:00")` has result `null` ) | ||
*/ | ||
@Test | ||
public void testOverlapWithWrongTypes() { | ||
Map<String, Object> params = Map.of("start1", DateValue.parse("1999"), | ||
"end1", DateValue.parse("2000"), | ||
"start2", LocalTimeValue.parse("19:00"), | ||
"end2", LocalTimeValue.parse("20:00"), | ||
"conf", Map.of()); | ||
|
||
Object output = singleResultFirstColumn(db, RETURN_OVERLAP, params); | ||
assertNull(output); | ||
} | ||
|
||
} |