Skip to content

Commit

Permalink
Fixes #1464: Create apoc.temporal.overlap(start1, end1, start2, end2,…
Browse files Browse the repository at this point in the history
… acceptAdjacentSpans:true) function
  • Loading branch information
vga91 committed Mar 14, 2024
1 parent 5e257d3 commit ca714f8
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 0 deletions.
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 docs/asciidoc/modules/ROOT/pages/overview/apoc.temporal/index.adoc
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[]
|===
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,15 @@ apoc.static.getAll(prefix) - returns statically stored values from config (apoc.
|label:procedure[]
|===

== xref::overview/apoc.temporal/index.adoc[]
[.procedures, opts=header, cols='5a,1a']
|===
| Qualified Name | Type
|xref::overview/apoc.temporal/apoc.temporal.overlap.adoc[apoc.temporal.overlap icon:book[]]

|label:function[]
|===

== xref::overview/apoc.trigger/index.adoc[]

[.procedures, opts=header, cols='5a,1a']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ This file is generated by DocsTest, so don't change it!
*** xref::overview/apoc.systemdb/apoc.systemdb.execute.adoc[]
*** xref::overview/apoc.systemdb/apoc.systemdb.export.metadata.adoc[]
*** xref::overview/apoc.systemdb/apoc.systemdb.graph.adoc[]
** xref::overview/apoc.temporal/index.adoc[]
*** xref::overview/apoc.temporal/apoc.temporal.overlap.adoc[]
** xref::overview/apoc.trigger/index.adoc[]
*** xref::overview/apoc.trigger/apoc.trigger.nodesByLabel.adoc[]
*** xref::overview/apoc.trigger/apoc.trigger.propertiesByKey.adoc[]
Expand Down
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 extended/src/main/java/apoc/temporal/TemporalExtended.java
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 extended/src/test/java/apoc/temporal/TemporalExtendedTest.java
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);
}

}

0 comments on commit ca714f8

Please sign in to comment.