Skip to content

Commit

Permalink
Add procedure compiler to Neo4j
Browse files Browse the repository at this point in the history
This is a repackaging of github.com/fbiville/neo4j-sproc-compiler
(branch 3.1) and has been relicensed to GPLv3 in the process.

This module aims at providing compile-time feedback on user-defined
functions and procedures, thus reducing the development feedback
loop.

As noted in the README, this does not and cannot entirely replace
the existing runtime validation procedure. The compile-time
annotation processor processes only, by definition, a single
compilation unit at a time.

For instance, duplicate detection can only be partially checked at
compile-time.
  • Loading branch information
Florent Biville committed Oct 1, 2016
1 parent 7b890ce commit a4d0389
Show file tree
Hide file tree
Showing 93 changed files with 6,492 additions and 0 deletions.
1 change: 1 addition & 0 deletions community/pom.xml
Expand Up @@ -65,6 +65,7 @@
<module>licensecheck-config</module>
<module>dbms</module>
<module>command-line</module>
<module>procedure-compiler</module>
</modules>

<licenses>
Expand Down
28 changes: 28 additions & 0 deletions community/procedure-compiler/README.md
@@ -0,0 +1,28 @@
# Neo4j Tooling - Procedure|User Function Compiler

This is a annotation processor that will verify your stored procedures
at compile time.

While most of the basic checks can be performed, you still need
some unit tests to verify some runtime behaviours.


# What does it do?

Once the stored procedure compiler is added into your project classpath (see Maven/Gradle
instructions below), it will trigger compilation failures if any of the following requirements
is not met:

- `@Context` fields must be `public` and non-`final`
- all other fields must be `static`
- `Map` record fields/procedure parameters must define their key type as `String`
- `@Procedure` class must define a public constructor with no arguments
- `@Procedure` method must return a Stream
- `@Procedure` parameter and record types must be supported
- `@Procedure` parameters must be annotated with `@Name`
- all visited `@Procedure` names must be unique*

*A deployed Neo4j instance can aggregate stored procedures from different JARs.
Inter-JAR naming conflict cannot be detected by an annotation processor.
By definition, it can only inspect one compilation unit at a time.

100 changes: 100 additions & 0 deletions community/procedure-compiler/integration-tests/pom.xml
@@ -0,0 +1,100 @@
<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>


<parent>
<groupId>org.neo4j</groupId>
<artifactId>procedure-compiler-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

<artifactId>procedure-compiler-integration-tests</artifactId>
<packaging>jar</packaging>

<name>Neo4j - Procedure Compiler Integration Tests</name>

<dependencies>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>procedure-compiler</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>procedure-compiler</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<type>test-jar</type>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j.test</groupId>
<artifactId>neo4j-harness</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>1.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>default-jar</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<executions>
<execution>
<id>default-install</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2002-2016 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.tooling.procedure;

import org.neo4j.tooling.procedure.procedures.valid.Procedures;
import org.junit.Rule;
import org.junit.Test;

import org.neo4j.driver.v1.Config;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.GraphDatabase;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.harness.junit.Neo4jRule;

import static org.assertj.core.api.Assertions.assertThat;


public class ProcedureTest
{

private static final Class<?> PROCEDURES_CLASS = Procedures.class;

@Rule
public Neo4jRule graphDb = new Neo4jRule().withProcedure( PROCEDURES_CLASS );
private String procedureNamespace = PROCEDURES_CLASS.getPackage().getName();

@Test
public void calls_simplistic_procedure()
{
try ( Driver driver = GraphDatabase.driver( graphDb.boltURI(), configuration() );
Session session = driver.session() )
{

StatementResult result = session.run( "CALL " + procedureNamespace + ".theAnswer()" );

assertThat( result.single().get( "value" ).asLong() ).isEqualTo( 42L );
}
}

@Test
public void calls_procedures_with_simple_input_type_returning_void()
{
try ( Driver driver = GraphDatabase.driver( graphDb.boltURI(), configuration() );
Session session = driver.session() )
{

session.run( "CALL " + procedureNamespace + ".simpleInput00()" );
session.run( "CALL " + procedureNamespace + ".simpleInput01('string')" );
session.run( "CALL " + procedureNamespace + ".simpleInput02(42)" );
session.run( "CALL " + procedureNamespace + ".simpleInput03(42)" );
session.run( "CALL " + procedureNamespace + ".simpleInput04(4.2)" );
session.run( "CALL " + procedureNamespace + ".simpleInput05(true)" );
session.run( "CALL " + procedureNamespace + ".simpleInput06(false)" );
session.run( "CALL " + procedureNamespace + ".simpleInput07({foo:'bar'})" );
session.run( "MATCH (n) CALL " + procedureNamespace + ".simpleInput08(n) RETURN n" );
session.run( "MATCH p=(()-[r]->()) CALL " + procedureNamespace + ".simpleInput09(p) RETURN p" );
session.run( "MATCH ()-[r]->() CALL " + procedureNamespace + ".simpleInput10(r) RETURN r" );
}
}

@Test
public void calls_procedures_with_simple_input_type_returning_record_with_primitive_fields()
{
try ( Driver driver = GraphDatabase.driver( graphDb.boltURI(), configuration() );
Session session = driver.session() )
{

assertThat( session.run( "CALL " + procedureNamespace + ".simpleInput11('string')" ).single() ).isNotNull();
assertThat( session.run( "CALL " + procedureNamespace + ".simpleInput12(42)" ).single() ).isNotNull();
assertThat( session.run( "CALL " + procedureNamespace + ".simpleInput13(42)" ).single() ).isNotNull();
assertThat( session.run( "CALL " + procedureNamespace + ".simpleInput14(4.2)" ).single() ).isNotNull();
assertThat( session.run( "CALL " + procedureNamespace + ".simpleInput15(true)" ).single() ).isNotNull();
assertThat( session.run( "CALL " + procedureNamespace + ".simpleInput16(false)" ).single() ).isNotNull();
assertThat( session.run( "CALL " + procedureNamespace + ".simpleInput17({foo:'bar'})" ).single() )
.isNotNull();
assertThat( session.run( "CALL " + procedureNamespace + ".simpleInput21()" ).single() ).isNotNull();
}

}

private Config configuration()
{
return Config.build().withEncryptionLevel( Config.EncryptionLevel.NONE ).toConfig();
}

}
73 changes: 73 additions & 0 deletions community/procedure-compiler/pom.xml
@@ -0,0 +1,73 @@
<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">
<parent>
<groupId>org.neo4j</groupId>
<artifactId>parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<relativePath>../..</relativePath>
</parent>

<properties>
<short-name>procedure-compiler</short-name>
<bundle.namespace>org.neo4j.tooling.procedure</bundle.namespace>
<license-text.header>GPL-3-header.txt</license-text.header>
<licensing.prepend.text>notice-gpl-prefix.txt</licensing.prepend.text>
<version-package>tooling.procedure</version-package>
</properties>

<modelVersion>4.0.0</modelVersion>
<artifactId>procedure-compiler-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Neo4j - Procedure Compiler Parent POM</name>
<description>Neo4j Stored Procedure compile-time annotation processor</description>
<url>http://components.neo4j.org/${project.artifactId}/${project.version}</url>

<scm>
<connection>scm:git:git://github.com/neo4j/neo4j.git</connection>
<developerConnection>scm:git:git@github.com:neo4j/neo4j.git</developerConnection>
<url>https://github.com/neo4j/neo4j</url>
</scm>

<licenses>
<license>
<name>GNU General Public License, Version 3</name>
<url>http://www.gnu.org/licenses/gpl-3.0-standalone.html</url>
<comments>The software ("Software") developed and owned by Network Engine for
Objects in Lund AB (referred to in this notice as "Neo Technology") is
licensed under the GNU GENERAL PUBLIC LICENSE Version 3 to all third
parties and that license is included below.

However, if you have executed an End User Software License and Services
Agreement or an OEM Software License and Support Services Agreement, or
another commercial license agreement with Neo Technology or one of its
affiliates (each, a "Commercial Agreement"), the terms of the license in
such Commercial Agreement will supersede the GNU GENERAL PUBLIC LICENSE
Version 3 and you may use the Software solely pursuant to the terms of
the relevant Commercial Agreement.
</comments>
</license>
</licenses>

<modules>
<module>processor</module>
<module>integration-tests</module>
</modules>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.4.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

0 comments on commit a4d0389

Please sign in to comment.