Skip to content

Commit

Permalink
Added built-in procedure to list all functions
Browse files Browse the repository at this point in the history
Added `dbms.functions` that lists all created user function in the same way
that we have the `dbms.procedures`.
  • Loading branch information
pontusmelke committed Sep 13, 2016
1 parent c17f14c commit 06b3ad8
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 13 deletions.
Expand Up @@ -560,6 +560,9 @@ DoubleLongRegister indexSample( IndexDescriptor index, DoubleLongRegister target
/** Fetch a function given its signature, or <code>empty</code> if no such function exists*/ /** Fetch a function given its signature, or <code>empty</code> if no such function exists*/
Optional<UserFunctionSignature> functionGet( QualifiedName name ); Optional<UserFunctionSignature> functionGet( QualifiedName name );


/** Fetch all registered procedures */
Set<UserFunctionSignature> functionsGetAll();

/** Fetch all registered procedures */ /** Fetch all registered procedures */
Set<ProcedureSignature> proceduresGetAll(); Set<ProcedureSignature> proceduresGetAll();


Expand Down
Expand Up @@ -37,6 +37,7 @@
import org.neo4j.kernel.api.exceptions.index.IndexNotFoundKernelException; import org.neo4j.kernel.api.exceptions.index.IndexNotFoundKernelException;
import org.neo4j.kernel.api.index.IndexDescriptor; import org.neo4j.kernel.api.index.IndexDescriptor;
import org.neo4j.kernel.api.proc.ProcedureSignature; import org.neo4j.kernel.api.proc.ProcedureSignature;
import org.neo4j.kernel.api.proc.UserFunctionSignature;
import org.neo4j.kernel.impl.api.TokenAccess; import org.neo4j.kernel.impl.api.TokenAccess;
import org.neo4j.procedure.Context; import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description; import org.neo4j.procedure.Description;
Expand All @@ -47,6 +48,7 @@
import static org.neo4j.helpers.collection.Iterators.asSet; import static org.neo4j.helpers.collection.Iterators.asSet;
import static org.neo4j.procedure.Mode.READ; import static org.neo4j.procedure.Mode.READ;


@SuppressWarnings( "unused" )
public class BuiltInProcedures public class BuiltInProcedures
{ {
@Context @Context
Expand Down Expand Up @@ -160,6 +162,20 @@ public Stream<ProcedureResult> listProcedures()
} }
} }


@Description( "List all user functions in the DBMS." )
@Procedure(name = "dbms.functions", mode = READ)
public Stream<FunctionResult> listFunctions()
{
try ( Statement statement = tx.acquireStatement() )
{
return statement.readOperations().functionsGetAll()
.stream()
.sorted( ( a, b ) -> a.name().toString().compareTo( b.name().toString() ) )
.map( FunctionResult::new );
}
}

@SuppressWarnings( "unused" )
public class LabelResult public class LabelResult
{ {
public final String label; public final String label;
Expand All @@ -170,6 +186,7 @@ private LabelResult( Label label )
} }
} }


@SuppressWarnings( "unused" )
public class PropertyKeyResult public class PropertyKeyResult
{ {
public final String propertyKey; public final String propertyKey;
Expand All @@ -180,6 +197,7 @@ private PropertyKeyResult( String propertyKey )
} }
} }


@SuppressWarnings( "unused" )
public class RelationshipTypeResult public class RelationshipTypeResult
{ {
public final String relationshipType; public final String relationshipType;
Expand All @@ -190,6 +208,7 @@ private RelationshipTypeResult( RelationshipType relationshipType )
} }
} }


@SuppressWarnings( "unused" )
public class IndexResult public class IndexResult
{ {
public final String description; public final String description;
Expand All @@ -204,6 +223,7 @@ private IndexResult( String description, String state, String type )
} }
} }


@SuppressWarnings( "unused" )
public class ConstraintResult public class ConstraintResult
{ {
public final String description; public final String description;
Expand All @@ -228,6 +248,21 @@ private ProcedureResult( ProcedureSignature signature )
} }
} }


@SuppressWarnings( "unused" )
public class FunctionResult
{
public final String name;
public final String signature;
public final String description;

private FunctionResult( UserFunctionSignature signature )
{
this.name = signature.name().toString();
this.signature = signature.toString();
this.description = signature.description().orElse( "" );
}
}

//When we have decided on what to call different indexes //When we have decided on what to call different indexes
//this should probably be moved to some more central place //this should probably be moved to some more central place
private enum IndexType private enum IndexType
Expand Down
Expand Up @@ -557,6 +557,13 @@ public Optional<UserFunctionSignature> functionGet( QualifiedName name )
return procedures.function( name ); return procedures.function( name );
} }


@Override
public Set<UserFunctionSignature> functionsGetAll()
{
statement.assertOpen();
return procedures.getAllFunctions();
}

@Override @Override
public Object functionCall( QualifiedName name, Object[] input ) throws ProcedureException public Object functionCall( QualifiedName name, Object[] input ) throws ProcedureException
{ {
Expand Down
Expand Up @@ -19,17 +19,17 @@
*/ */
package org.neo4j.kernel.builtinprocs; package org.neo4j.kernel.builtinprocs;


import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
import org.mockito.stubbing.Answer;

import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;


import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
import org.mockito.stubbing.Answer;

import org.neo4j.helpers.collection.Iterators; import org.neo4j.helpers.collection.Iterators;
import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.api.ReadOperations; import org.neo4j.kernel.api.ReadOperations;
Expand All @@ -48,7 +48,6 @@


import static java.util.Collections.emptyIterator; import static java.util.Collections.emptyIterator;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;

import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
Expand All @@ -57,7 +56,6 @@
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;

import static org.neo4j.kernel.api.proc.Context.KERNEL_TRANSACTION; import static org.neo4j.kernel.api.proc.Context.KERNEL_TRANSACTION;


public class BuiltInProceduresTest public class BuiltInProceduresTest
Expand Down Expand Up @@ -169,7 +167,7 @@ public void shouldEscapeLabelNameContainingColons() throws Throwable
public void shouldListCorrectBuiltinProcedures() throws Throwable public void shouldListCorrectBuiltinProcedures() throws Throwable
{ {
// When/Then // When/Then
assertThat( call( "dbms.procedures" ), contains( assertThat( call( "dbms.procedures" ), containsInAnyOrder(
record( "db.awaitIndex", "db.awaitIndex(label :: STRING?, property :: STRING?, timeOutSeconds = 300 :: INTEGER?) :: VOID", "Await indexes in the database to come online." ), record( "db.awaitIndex", "db.awaitIndex(label :: STRING?, property :: STRING?, timeOutSeconds = 300 :: INTEGER?) :: VOID", "Await indexes in the database to come online." ),
record( "db.constraints", "db.constraints() :: (description :: STRING?)", "List all constraints in the database." ), record( "db.constraints", "db.constraints() :: (description :: STRING?)", "List all constraints in the database." ),
record( "db.indexes", "db.indexes() :: (description :: STRING?, state :: STRING?, type :: STRING?)", "List all indexes in the database." ), record( "db.indexes", "db.indexes() :: (description :: STRING?, state :: STRING?, type :: STRING?)", "List all indexes in the database." ),
Expand All @@ -178,6 +176,7 @@ public void shouldListCorrectBuiltinProcedures() throws Throwable
record( "db.relationshipTypes", "db.relationshipTypes() :: (relationshipType :: STRING?)", "List all relationship types in the database." ), record( "db.relationshipTypes", "db.relationshipTypes() :: (relationshipType :: STRING?)", "List all relationship types in the database." ),
record( "dbms.components", "dbms.components() :: (name :: STRING?, versions :: LIST? OF STRING?, edition :: STRING?)", "List DBMS components and their versions." ), record( "dbms.components", "dbms.components() :: (name :: STRING?, versions :: LIST? OF STRING?, edition :: STRING?)", "List DBMS components and their versions." ),
record( "dbms.procedures", "dbms.procedures() :: (name :: STRING?, signature :: STRING?, description :: STRING?)", "List all procedures in the DBMS." ), record( "dbms.procedures", "dbms.procedures() :: (name :: STRING?, signature :: STRING?, description :: STRING?)", "List all procedures in the DBMS." ),
record( "dbms.functions", "dbms.functions() :: (name :: STRING?, signature :: STRING?, description :: STRING?)", "List all user functions in the DBMS." ),
record( "dbms.queryJmx", "dbms.queryJmx(query :: STRING?) :: (name :: STRING?, description :: STRING?, attributes :: MAP?)", "Query JMX management data by domain and name. For instance, \"org.neo4j:*\"") record( "dbms.queryJmx", "dbms.queryJmx(query :: STRING?) :: (name :: STRING?, description :: STRING?, attributes :: MAP?)", "Query JMX management data by domain and name. For instance, \"org.neo4j:*\"")
) ); ) );
} }
Expand Down
Expand Up @@ -115,6 +115,8 @@ public void listProcedures() throws Throwable
"STRING?)", "List all relationship types in the database."} ), "STRING?)", "List all relationship types in the database."} ),
equalTo( new Object[]{"dbms.procedures", "dbms.procedures() :: (name :: STRING?, signature :: " + equalTo( new Object[]{"dbms.procedures", "dbms.procedures() :: (name :: STRING?, signature :: " +
"STRING?, description :: STRING?)", "List all procedures in the DBMS."} ), "STRING?, description :: STRING?)", "List all procedures in the DBMS."} ),
equalTo( new Object[]{"dbms.functions", "dbms.functions() :: (name :: STRING?, signature :: " +
"STRING?, description :: STRING?)", "List all user functions in the DBMS."} ),
equalTo( new Object[]{"dbms.components", "dbms.components() :: (name :: STRING?, versions :: LIST? OF" + equalTo( new Object[]{"dbms.components", "dbms.components() :: (name :: STRING?, versions :: LIST? OF" +
" STRING?, edition :: STRING?)", "List DBMS components and their versions."} ), " STRING?, edition :: STRING?)", "List DBMS components and their versions."} ),
equalTo( new Object[]{"dbms.queryJmx", "dbms.queryJmx(query :: STRING?) :: (name :: STRING?, " + equalTo( new Object[]{"dbms.queryJmx", "dbms.queryJmx(query :: STRING?) :: (name :: STRING?, " +
Expand Down
Expand Up @@ -55,7 +55,7 @@ public class UserFunctionsTest
private final CallableUserFunction function = function( signature ); private final CallableUserFunction function = function( signature );


@Test @Test
public void shouldGetRegisteredProcedure() throws Throwable public void shouldGetRegisteredFunction() throws Throwable
{ {
// When // When
procs.register( function ); procs.register( function );
Expand All @@ -65,7 +65,7 @@ public void shouldGetRegisteredProcedure() throws Throwable
} }


@Test @Test
public void shouldGetAllRegisteredProcedures() throws Throwable public void shouldGetAllRegisteredFunctions() throws Throwable
{ {
// When // When
procs.register( function( functionSignature( "org", "myproc1" ).out(Neo4jTypes.NTAny).build() ) ); procs.register( function( functionSignature( "org", "myproc1" ).out(Neo4jTypes.NTAny).build() ) );
Expand All @@ -81,7 +81,7 @@ public void shouldGetAllRegisteredProcedures() throws Throwable
} }


@Test @Test
public void shouldCallRegisteredProcedure() throws Throwable public void shouldCallRegisteredFunction() throws Throwable
{ {
// Given // Given
procs.register( function ); procs.register( function );
Expand All @@ -96,7 +96,7 @@ public void shouldCallRegisteredProcedure() throws Throwable
} }


@Test @Test
public void shouldNotAllowCallingNonExistingProcedure() throws Throwable public void shouldNotAllowCallingNonExistingFunction() throws Throwable
{ {
// Expect // Expect
exception.expect( ProcedureException.class ); exception.expect( ProcedureException.class );
Expand Down Expand Up @@ -125,7 +125,7 @@ public void shouldNotAllowRegisteringConflictingName() throws Throwable
} }


@Test @Test
public void shouldSignalNonExistingProcedure() throws Throwable public void shouldSignalNonExistingFunction() throws Throwable
{ {
// When // When
assertThat(procs.function( signature.name() ), is( Optional.empty())); assertThat(procs.function( signature.name() ), is( Optional.empty()));
Expand Down
Expand Up @@ -65,6 +65,7 @@
import org.neo4j.logging.Log; import org.neo4j.logging.Log;
import org.neo4j.test.TestGraphDatabaseFactory; import org.neo4j.test.TestGraphDatabaseFactory;


import static java.lang.System.lineSeparator;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
Expand Down Expand Up @@ -678,6 +679,47 @@ public void shouldCallFunctionWithFourProvidedRestDefaultArgument() throws Throw
assertFalse( res.hasNext() ); assertFalse( res.hasNext() );
} }


@Test
public void shouldListAllFunctions() throws Throwable
{
//Given/When
Result res = db.execute( "CALL dbms.functions()" );

String expected =
"+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+" + lineSeparator() +
"| name | signature | description |" + lineSeparator() +
"+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+" + lineSeparator() +
"| 'org.neo4j.procedure.avgDoubleList' | 'org.neo4j.procedure.avgDoubleList(someValue :: LIST? OF FLOAT?) :: (FLOAT?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.avgNumberList' | 'org.neo4j.procedure.avgNumberList(someValue :: LIST? OF NUMBER?) :: (FLOAT?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.defaultValues' | 'org.neo4j.procedure.defaultValues(string = a string :: STRING?, integer = 42 :: INTEGER?, float = 3.14 :: FLOAT?, boolean = true :: BOOLEAN?) :: (STRING?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.delegatingFunction' | 'org.neo4j.procedure.delegatingFunction(someValue :: INTEGER?) :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.genericArguments' | 'org.neo4j.procedure.genericArguments(strings :: LIST? OF LIST? OF STRING?, longs :: LIST? OF LIST? OF LIST? OF INTEGER?) :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.indexOutOfBounds' | 'org.neo4j.procedure.indexOutOfBounds() :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.integrationTestMe' | 'org.neo4j.procedure.integrationTestMe() :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.listCoolPeopleInDatabase' | 'org.neo4j.procedure.listCoolPeopleInDatabase() :: (LIST? OF ANY?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.logAround' | 'org.neo4j.procedure.logAround() :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.mapArgument' | 'org.neo4j.procedure.mapArgument(map :: MAP?) :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.node' | 'org.neo4j.procedure.node(id :: INTEGER?) :: (NODE?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.nodeListArgument' | 'org.neo4j.procedure.nodeListArgument(nodes :: LIST? OF NODE?) :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.nodePaths' | 'org.neo4j.procedure.nodePaths(someValue :: NODE?) :: (PATH?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.nodeWithDescription' | 'org.neo4j.procedure.nodeWithDescription(someValue :: NODE?) :: (NODE?)' | 'This is a description' |" + lineSeparator() +
"| 'org.neo4j.procedure.readOnlyCallingWriteFunction' | 'org.neo4j.procedure.readOnlyCallingWriteFunction() :: (NODE?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.readOnlyCallingWriteProcedure' | 'org.neo4j.procedure.readOnlyCallingWriteProcedure() :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.readOnlyTryingToWrite' | 'org.neo4j.procedure.readOnlyTryingToWrite() :: (NODE?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.readOnlyTryingToWriteSchema' | 'org.neo4j.procedure.readOnlyTryingToWriteSchema() :: (STRING?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.recursiveSum' | 'org.neo4j.procedure.recursiveSum(someValue :: INTEGER?) :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.shutdown' | 'org.neo4j.procedure.shutdown() :: (STRING?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.simpleArgument' | 'org.neo4j.procedure.simpleArgument(someValue :: INTEGER?) :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.squareDouble' | 'org.neo4j.procedure.squareDouble(someValue :: FLOAT?) :: (FLOAT?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.squareLong' | 'org.neo4j.procedure.squareLong(someValue :: INTEGER?) :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.throwsExceptionInStream' | 'org.neo4j.procedure.throwsExceptionInStream() :: (INTEGER?)' | '' |" + lineSeparator() +
"| 'org.neo4j.procedure.unsupportedFunction' | 'org.neo4j.procedure.unsupportedFunction() :: (STRING?)' | '' |" + lineSeparator() +
"+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+" + lineSeparator() +
"25 rows" + lineSeparator();

assertThat(res.resultAsString(), equalTo(expected.replaceAll( "'", "\"" )));
}

@Before @Before
public void setUp() throws IOException public void setUp() throws IOException
{ {
Expand Down

0 comments on commit 06b3ad8

Please sign in to comment.