haha, good point. I guess I could give you a jar containing anything. Following is the source code for the custom function, junit 5 tests, and the pom file. You can use this to build the jar. Once you have maven project setup, you can build the jar with 'mvn package'. You can see its usage in the unit tests.
package customFunctions;
import org.neo4j.graphdb.Node;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.UserFunction;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class CustomFunctions {
@UserFunction()
@Description("Calculate the similarity between two nodes by verifying list of strings have at least one element in common")
public boolean isSimilar(@Name("aProp") String aProp, @Name("a") Node a, @Name("bProp") String bProp, @Name("b") Node b) {
Objects.requireNonNull(aProp);
Objects.requireNonNull(a);
Objects.requireNonNull(bProp);
Objects.requireNonNull(b);
if(!a.hasProperty(aProp) || !b.hasProperty(bProp)) {
return false;
}
Object list_intern = a.getProperty(aProp);
Object list_extern = b.getProperty(bProp);
boolean is_list_intern_a_list = list_intern.getClass().isArray();
boolean is_list_extern_a_list = list_extern.getClass().isArray();
if(is_list_intern_a_list && is_list_extern_a_list) {
List<String> list_intern_as_list = Arrays.asList((String[]) list_intern);
List<String> list_extern_as_list = Arrays.asList((String[]) list_extern);
return list_intern_as_list.stream().anyMatch(list_extern_as_list::contains);
}
if(is_list_intern_a_list) {
return Arrays.asList((String[])list_intern).contains(list_extern);
}
if(is_list_extern_a_list) {
return Arrays.asList((String[])list_extern).contains(list_intern);
}
return list_intern.equals(list_extern);
}
}
import customFunctions.CustomFunctions;
import org.junit.jupiter.api.*;
import org.neo4j.driver.*;
import org.neo4j.harness.Neo4j;
import org.neo4j.harness.Neo4jBuilders;
class CustomFunctionTests {
static Driver driver;
@BeforeAll
static void setup_db() {
Neo4j neo4j = Neo4jBuilders.newInProcessBuilder()
.withFunction(CustomFunctions.class)
.build();
driver = GraphDatabase.driver(neo4j.boltURI(), Config.builder()
.withoutEncryption()
.build());
}
@BeforeEach
void delete_data() {
try (Session session = driver.session()) {
session.run("match(n) detach delete n");
}
}
@Test
@DisplayName("a is null, ClientException is thrown")
void test_null_a_value_results_in_neo4j_client_exception() {
Assertions.assertThrows(org.neo4j.driver.exceptions.ClientException.class, ()->{
String cypher = "create (b{id: 2}) return customFunctions.isSimilar('list_intern', null, 'list_extern', b) as result";
executeCypher(cypher);
});
}
@Test
@DisplayName("b is null, Neo4j ClientException is thrown")
void test_null_b_value_results_in_neo4j_client_exception() {
Assertions.assertThrows(org.neo4j.driver.exceptions.ClientException.class, ()->{
String cypher = "create (a{id: 1}) return customFunctions.isSimilar('list_intern', a, 'list_extern', null) as result";
executeCypher(cypher);
});
}
@Test
@DisplayName("aProp is null, ClientException is thrown")
void test_null_aProp_value_results_in_neo4j_client_exception() {
Assertions.assertThrows(org.neo4j.driver.exceptions.ClientException.class, ()->{
String cypher = "create (a{id: 1}), (b{id: 2}) return customFunctions.isSimilar(null, a, 'list_extern', b) as result";
executeCypher(cypher);
});
}
@Test
@DisplayName("bProp is null, ClientException is thrown")
void test_null_bProp_value_results_in_neo4j_client_exception() {
Assertions.assertThrows(org.neo4j.driver.exceptions.ClientException.class, ()->{
String cypher = "create (a{id: 1}), (b{id: 2}) return customFunctions.isSimilar('list_intern', a, null, b) as result";
executeCypher(cypher);
});
}
@Test
@DisplayName("a has null list, b has null list")
void test_both_nodes_have_null_lists() {
String cypher = "create (a{id: 1}), (b{id: 2})";
boolean isSimilar = executeTest(cypher);
Assertions.assertFalse(isSimilar);
}
@Test
@DisplayName("a has null list, b has non-null list")
void test_a_node_has_null_lists_b_node_has_non_null_list() {
String cypher = "create (a{id: 1}), (b{id: 2, list_extern: ['1','2']})";
boolean isSimilar = executeTest(cypher);
Assertions.assertFalse(isSimilar);
}
@Test
@DisplayName("a has non-null list, b has null list")
void test_a_node_has_non_null_lists_b_node_has_null_list() {
String cypher = "create (a{id: 1, list_intern: ['1','2']}), (b{id: 2})";
boolean isSimilar = executeTest(cypher);
Assertions.assertFalse(isSimilar);
}
@Test
@DisplayName("a and b have lists of integers with no common elements")
void test_a_and_b_have_lists_with_no_common_elements() {
String cypher = "create (a{id: 1, list_intern: ['10','20']}), (b{id: 2, list_extern: ['1','2']})";
boolean isSimilar = executeTest(cypher);
Assertions.assertFalse(isSimilar);
}
@Test
@DisplayName("a and b have lists of integers with one common element")
void test_a_and_b_have_lists_with_one_common_element() {
String cypher = "create (a{id: 1, list_intern: ['10','2']}), (b{id: 2, list_extern: ['1','2']})";
boolean isSimilar = executeTest(cypher);
Assertions.assertTrue(isSimilar);
}
@Test
@DisplayName("a and b have lists of integers with multiple common element")
void test_a_and_b_have_lists_with_multiple_common_element() {
String cypher = "create (a{id: 1, list_intern: ['10','2','100']}), (b{id: 2, list_extern: ['100','1','2']})";
boolean isSimilar = executeTest(cypher);
Assertions.assertTrue(isSimilar);
}
@Test
@DisplayName("a has a single element and b has a list that contains the single element in a")
void test_a_has_a_single_element_and_b_has_a_list_that_contains_the_element_of_a() {
String cypher = "create (a{id: 1, list_intern: '2'}), (b{id: 2, list_extern: ['100','1','2']})";
boolean isSimilar = executeTest(cypher);
Assertions.assertTrue(isSimilar);
}
@Test
@DisplayName("b has a single element and a has a list that contains the single element in b")
void test_b_has_a_single_element_and_a_has_a_list_that_contains_the_element_of_b() {
String cypher = "create (a{id: 1, list_intern: ['100','1','2']}), (b{id: 2, list_extern: '2'})";
boolean isSimilar = executeTest(cypher);
Assertions.assertTrue(isSimilar);
}
@Test
@DisplayName("a has a single element and b has the single element equal to b")
void test_a_has_a_single_element_and_b_has_the_single_element_of_b() {
String cypher = "create (a{id: 1, list_intern: '2'}), (b{id: 2, list_extern: '2'})";
boolean isSimilar = executeTest(cypher);
Assertions.assertTrue(isSimilar);
}
@Test
@DisplayName("a and b have single elements that are not equal")
void test_a_and_b_have_a_single_element_that_are_not_equal() {
String cypher = "create (a{id: 1, list_intern: '2'}), (b{id: 2, list_extern: '4'})";
boolean isSimilar = executeTest(cypher);
Assertions.assertFalse(isSimilar);
}
private boolean executeTest(String setupCypher) {
try (Session session = driver.session()) {
session.run(setupCypher);
Result result = session.run("match (a{id: 1}), (b{id: 2}) return customFunctions.isSimilar('list_intern', a, 'list_extern', b) as result");
if (result.hasNext()) {
Record record = result.next();
return record.get("result").asBoolean();
} else {
throw new IllegalArgumentException();
}
}
}
private void executeCypher(String cypher) {
try (Session session = driver.session()) {
Result result = session.run(cypher);
}
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.domain.CustomFunction</groupId>
<artifactId>customFunction</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>Custom isSimilar Function</name>
<description>Prototype of a custom function to compare two nodes for similarity</description>
<properties>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<neo4j.version>4.4.6</neo4j.version>
<neo4j-java-driver.version>4.4.5</neo4j-java-driver.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<maven-failsafe-plugin.version>2.22.2</maven-failsafe-plugin.version>
</properties>
<dependencies>
<dependency>
<!-- This gives us the Procedure API our runtime code uses.
We have a `provided` scope on it, because when this is
deployed in a Neo4j Instance, the API will be provided
by Neo4j. If you add non-Neo4j dependencies to this
project, their scope should normally be `compile` -->
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>${neo4j.version}</version>
<scope>provided</scope>
</dependency>
<!-- Test Dependencies -->
<dependency>
<!-- This is used for a utility that lets us start Neo4j with
a specific Procedure, which is nice for writing tests. -->
<groupId>org.neo4j.test</groupId>
<artifactId>neo4j-harness</artifactId>
<version>${neo4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<!-- Used to send cypher statements to our procedure. -->
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>${neo4j-java-driver.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
</plugin>
<plugin>
<!-- This generates a jar-file with our procedure code,
plus any dependencies marked as `compile` scope.
This should then be deployed in the `plugins` directory
of each Neo4j instance in your deployment.
After a restart, the procedure is available for calling. -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>