Skip to content

Commit

Permalink
Merge branch '3.1' of github.com:neo4j/neo4j into 3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvest committed Jul 1, 2016
2 parents 5817d08 + 9310a64 commit 9a371ba
Show file tree
Hide file tree
Showing 19 changed files with 328 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ object ProcedureCallMode {
def fromAccessMode(mode: ProcedureAccessMode): ProcedureCallMode = mode match {
case ProcedureReadOnlyAccess => LazyReadOnlyCallMode
case ProcedureReadWriteAccess => EagerReadWriteCallMode
case ProcedureSchemaWriteAccess => SchemaWriteCallMode
case ProcedureDbmsAccess => DbmsCallMode
}
}
Expand Down Expand Up @@ -56,6 +57,19 @@ case object EagerReadWriteCallMode extends ProcedureCallMode {
}
}

case object SchemaWriteCallMode extends ProcedureCallMode {
override val queryType: InternalQueryType = SCHEMA_WRITE

override def call(ctx: QueryContext, name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] = {
val builder = ArrayBuffer.newBuilder[Array[AnyRef]]
val iterator = ctx.callSchemaWriteProcedure(name, args)
while (iterator.hasNext) {
builder += iterator.next()
}
builder.result().iterator
}
}

case object DbmsCallMode extends ProcedureCallMode {
override val queryType: InternalQueryType = DBMS

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ class DelegatingQueryContext(val inner: QueryContext) extends QueryContext {
override def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]) =
singleDbHit(inner.callReadWriteProcedure(name, args))

override def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]) =
singleDbHit(inner.callSchemaWriteProcedure(name, args))

override def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any]) =
inner.callDbmsProcedure(name, args)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ sealed trait ProcedureAccessMode

case object ProcedureReadOnlyAccess extends ProcedureAccessMode
case object ProcedureReadWriteAccess extends ProcedureAccessMode
case object ProcedureSchemaWriteAccess extends ProcedureAccessMode
case object ProcedureDbmsAccess extends ProcedureAccessMode
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ trait QueryContext extends TokenContext {

def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]]

def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]]

def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]]

// Check if a runtime value is a node, relationship, path or some such value returned from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ trait QueryContextAdaptation {

override def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]): scala.Iterator[Array[AnyRef]] = ???

override def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] = ???

override def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] = ???

override def getOrCreateFromSchemaState[K, V](key: K, creator: => V): V = ???
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class ExceptionTranslatingQueryContextFor3_1(val inner: QueryContext) extends Qu
override def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] =
translateIterator(inner.callReadWriteProcedure(name, args))

override def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] =
translateIterator(inner.callSchemaWriteProcedure(name, args))

override def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] =
translateIterator(inner.callDbmsProcedure(name, args))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class TransactionBoundPlanContext(tc: TransactionalContextWrapperv3_1)
private def asCypherProcMode(mode: KernelProcedureSignature.Mode): ProcedureAccessMode = mode match {
case KernelProcedureSignature.Mode.READ_ONLY => ProcedureReadOnlyAccess
case KernelProcedureSignature.Mode.READ_WRITE => ProcedureReadWriteAccess
case KernelProcedureSignature.Mode.SCHEMA_WRITE => ProcedureSchemaWriteAccess
case KernelProcedureSignature.Mode.DBMS => ProcedureDbmsAccess
case _ => throw new CypherExecutionException(
"Unable to execute procedure, because it requires an unrecognized execution mode: " + mode.name(), null )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,9 @@ final class TransactionBoundQueryContext(val transactionalContext: Transactional
override def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]) =
callProcedure(name, args, transactionalContext.statement.dataWriteOperations().procedureCallWrite)

override def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any]) =
callProcedure(name, args, transactionalContext.statement.schemaWriteOperations().procedureCallSchema)

override def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any]) =
callProcedure(name, args, transactionalContext.dbmsOperations.procedureCallDbms(_, _, transactionalContext.accessMode))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@
*/
package org.neo4j.kernel.api;

import org.neo4j.collection.RawIterator;
import org.neo4j.kernel.api.constraints.NodePropertyConstraint;
import org.neo4j.kernel.api.constraints.NodePropertyExistenceConstraint;
import org.neo4j.kernel.api.constraints.RelationshipPropertyConstraint;
import org.neo4j.kernel.api.constraints.RelationshipPropertyExistenceConstraint;
import org.neo4j.kernel.api.constraints.UniquenessConstraint;
import org.neo4j.kernel.api.exceptions.ProcedureException;
import org.neo4j.kernel.api.exceptions.schema.AlreadyConstrainedException;
import org.neo4j.kernel.api.exceptions.schema.AlreadyIndexedException;
import org.neo4j.kernel.api.exceptions.schema.CreateConstraintFailureException;
import org.neo4j.kernel.api.exceptions.schema.DropConstraintFailureException;
import org.neo4j.kernel.api.exceptions.schema.DropIndexFailureException;
import org.neo4j.kernel.api.index.IndexDescriptor;
import org.neo4j.kernel.api.proc.ProcedureSignature;

public interface SchemaWriteOperations extends TokenWriteOperations
{
Expand Down Expand Up @@ -61,4 +64,7 @@ RelationshipPropertyExistenceConstraint relationshipPropertyExistenceConstraintC
* That external job should become an internal job, at which point this operation should go away.
*/
void uniqueIndexDrop( IndexDescriptor descriptor ) throws DropIndexFailureException;

/** Invoke a schema procedure by name */
RawIterator<Object[], ProcedureException> procedureCallSchema( ProcedureSignature.ProcedureName name, Object[] input ) throws ProcedureException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ public enum Mode
READ_ONLY,
/** This procedure may perform both read and write operations against the graph */
READ_WRITE,
/** This procedure will perform operations against the schema */
SCHEMA_WRITE,
/** This procedure will perform system operations - i.e. not against the graph */
DBMS
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,12 @@ public void uniqueIndexDrop( IndexDescriptor descriptor ) throws DropIndexFailur
schemaWrite().uniqueIndexDrop( statement, descriptor );
}

@Override
public RawIterator<Object[], ProcedureException> procedureCallSchema( ProcedureName name, Object[] input ) throws ProcedureException
{
return callProcedure( name, input, AccessMode.Static.FULL );
}

// </SchemaWrite>

// <Locking>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import org.neo4j.kernel.api.proc.ProcedureSignature.FieldSignature;
import org.neo4j.kernel.api.proc.ProcedureSignature.ProcedureName;
import org.neo4j.kernel.impl.proc.OutputMappers.OutputMapper;
import org.neo4j.procedure.PerformsDBMS;
import org.neo4j.procedure.PerformsWrites;
import org.neo4j.procedure.Procedure;

Expand Down Expand Up @@ -109,13 +108,30 @@ private ReflectiveProcedure compileProcedure( Class<?> procDefinition, MethodHan
List<FieldInjections.FieldSetter> setters = fieldInjections.setters( procDefinition );

ProcedureSignature.Mode mode = ProcedureSignature.Mode.READ_ONLY;
if ( method.isAnnotationPresent( PerformsWrites.class ) )
Procedure procedure = method.getAnnotation( Procedure.class );
if ( procedure.mode().equals( Procedure.Mode.DBMS ) )
{
mode = ProcedureSignature.Mode.DBMS;
}
else if ( procedure.mode().equals( Procedure.Mode.SCHEMA ) )
{
mode = ProcedureSignature.Mode.SCHEMA_WRITE;
}
else if ( procedure.mode().equals( Procedure.Mode.WRITE ) )
{
mode = ProcedureSignature.Mode.READ_WRITE;
}
else if ( method.isAnnotationPresent( PerformsDBMS.class ) )
if ( method.isAnnotationPresent( PerformsWrites.class ) )
{
mode = ProcedureSignature.Mode.DBMS;
if ( mode == ProcedureSignature.Mode.DBMS || mode == ProcedureSignature.Mode.SCHEMA_WRITE )
{
throw new ProcedureException( Status.Procedure.ProcedureRegistrationFailed,
"Conflicting procedure annotation, PerformsWrites and mode = %s.", procedure.mode() );
}
else
{
mode = ProcedureSignature.Mode.READ_WRITE;
}
}

ProcedureSignature signature = new ProcedureSignature( procName, inputSignature, outputMapper.signature(), mode );
Expand All @@ -140,10 +156,12 @@ private MethodHandle constructor( Class<?> procDefinition ) throws ProcedureExce

private ProcedureName extractName( Class<?> procDefinition, Method m )
{
String definedName = m.getAnnotation( Procedure.class ).value();
if( definedName.trim().length() > 0 )
String valueName = m.getAnnotation( Procedure.class ).value();
String definedName = m.getAnnotation( Procedure.class ).name();
String procName = (definedName.trim().isEmpty() ? valueName : definedName);
if( procName.trim().length() > 0 )
{
String[] split = definedName.split( "\\." );
String[] split = procName.split( "\\." );
if( split.length == 1)
{
return new ProcedureName( new String[0], split[0] );
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* <p>
* This is <i>required</i> if the procedure performs write operations.
*/
@Deprecated
@Target( ElementType.METHOD )
@Retention( RetentionPolicy.RUNTIME )
public @interface PerformsWrites
Expand Down
28 changes: 25 additions & 3 deletions community/kernel/src/main/java/org/neo4j/procedure/Procedure.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@
* {@link java.util.stream.Stream} of {@code Records}. The work performed usually
* involves one or more resources, such as a {@link org.neo4j.graphdb.GraphDatabaseService}.
* <p>
* By default, procedures are read-only. If you want to perform writes to the
* database, you need to add the {@link PerformsWrites} annotation to your method as well.
* A procedure is associated with one of the following modes
* READ allows only reading the graph (default mode)
* WRITE allows reading and writing the graph
* SCHEMA allows reading the graphs and performing schema operations
* DBMS allows managing the database (i.e. change password)
*
* <h2>Input declaration</h2>
* A procedure can accept input arguments, which is defined in the arguments to the
Expand All @@ -54,7 +57,7 @@
* </ul>
*
* <h2>Output declaration</h2>
* A procedure must always return a {@link java.util.stream.Stream} of {@code Records}.
* A procedure must always return a {@link java.util.stream.Stream} of {@code Records}, or nothing.
* The record is defined per procedure, as a class with only public, non-final fields.
* The types, order and names of the fields in this class define the format of the returned records.
* <p>
Expand Down Expand Up @@ -112,4 +115,23 @@
* @return the namespace and procedure name
*/
String value() default "";

/**
* Synonym for {@link #value()}
*/
String name() default "";

/**
* A procedure is associated with one of the following modes
* READ allows only reading the graph (default mode)
* WRITE allows reading and writing the graph
* SCHEMA allows reading the graphs and performing schema operations
* DBMS allows managing the database (i.e. change password)
*/
Mode mode() default Mode.READ;

enum Mode
{
READ, WRITE, SCHEMA, DBMS
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@
import org.neo4j.kernel.api.exceptions.ProcedureException;
import org.neo4j.kernel.api.proc.CallableProcedure;
import org.neo4j.kernel.api.proc.ProcedureSignature;
import org.neo4j.procedure.PerformsWrites;
import org.neo4j.procedure.Procedure;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.neo4j.helpers.collection.Iterators.asList;
import static org.neo4j.kernel.api.proc.CallableProcedure.Key.key;
Expand Down Expand Up @@ -183,6 +186,65 @@ public RawIterator<Object[], ProcedureException> apply( Context ctx, Object[] in
assertThat( asList( result ), contains( equalTo( new Object[]{ "hello, world" } ) ) );
}

@Test
public void shouldCompileProcedureWithPerformsWrites() throws Throwable
{
procs.register( ProcedureWithPerformsWritesAndRead.class );
assertNotNull( procs.get( new ProcedureSignature.ProcedureName( "org.neo4j.kernel.impl.proc".split( "\\." ),
"shouldCompile" ) ) );
assertNotNull( procs.get( new ProcedureSignature.ProcedureName( "org.neo4j.kernel.impl.proc".split( "\\." ),
"shouldCompileToo" ) ) );
}

@Test
public void shouldFailCompileProcedureWithDBMSConflict() throws Throwable
{
exception.expect( ProcedureException.class );
exception.expectMessage( "Conflicting procedure annotation, PerformsWrites and mode = DBMS." );
procs.register( ProcedureWithDBMSConflictAnnotation.class );
}

@Test
public void shouldFailCompileProcedureWithSchemaConflict() throws Throwable
{
exception.expect( ProcedureException.class );
exception.expectMessage( "Conflicting procedure annotation, PerformsWrites and mode = SCHEMA." );
procs.register( ProcedureWithSchemaConflictAnnotation.class );
}

public static class ProcedureWithPerformsWritesAndRead
{
@PerformsWrites
@Procedure( mode = Procedure.Mode.READ )
public void shouldCompile()
{
}

@PerformsWrites
@Procedure( mode = Procedure.Mode.WRITE )
public void shouldCompileToo()
{
}
}

public static class ProcedureWithDBMSConflictAnnotation
{
@PerformsWrites
@Procedure( mode = Procedure.Mode.DBMS )
public void shouldNotCompile()
{
}
}

public static class ProcedureWithSchemaConflictAnnotation
{
@PerformsWrites
@Procedure( mode = Procedure.Mode.SCHEMA )
public void shouldNotCompile()
{
}
}

private CallableProcedure.BasicProcedure procedureWithSignature( final ProcedureSignature signature )
{
return new CallableProcedure.BasicProcedure(signature)
Expand Down

0 comments on commit 9a371ba

Please sign in to comment.