Skip to content

Commit

Permalink
Avoid changing the db.awaitIndex and db.resampleIndex behaviours,…
Browse files Browse the repository at this point in the history
… and instead add a new `db.index.fulltext.awaitIndex` procedure for awaiting indexes by name.

The reason for this is that fears were expressed about the change to the `db.awaitIndex` procedure being an incompatible change.
  • Loading branch information
chrisvest committed Dec 20, 2018
1 parent d2f50cd commit 96ca2f8
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 52 deletions.
Expand Up @@ -100,7 +100,7 @@ public class FulltextProceduresTest
private static final String DB_INDEXES = "CALL db.indexes"; private static final String DB_INDEXES = "CALL db.indexes";
private static final String DROP = "CALL db.index.fulltext.drop(\"%s\")"; private static final String DROP = "CALL db.index.fulltext.drop(\"%s\")";
private static final String LIST_AVAILABLE_ANALYZERS = "CALL db.index.fulltext.listAvailableAnalyzers()"; private static final String LIST_AVAILABLE_ANALYZERS = "CALL db.index.fulltext.listAvailableAnalyzers()";
private static final String DB_AWAIT_INDEX = "CALL db.awaitIndex(\"%s\")"; private static final String DB_AWAIT_INDEX = "CALL db.index.fulltext.awaitIndex(\"%s\")";
static final String QUERY_NODES = "CALL db.index.fulltext.queryNodes(\"%s\", \"%s\")"; static final String QUERY_NODES = "CALL db.index.fulltext.queryNodes(\"%s\", \"%s\")";
static final String QUERY_RELS = "CALL db.index.fulltext.queryRelationships(\"%s\", \"%s\")"; static final String QUERY_RELS = "CALL db.index.fulltext.queryRelationships(\"%s\", \"%s\")";
static final String AWAIT_REFRESH = "CALL db.index.fulltext.awaitEventuallyConsistentIndexRefresh()"; static final String AWAIT_REFRESH = "CALL db.index.fulltext.awaitEventuallyConsistentIndexRefresh()";
Expand Down
Expand Up @@ -137,13 +137,11 @@ public void listProcedures() throws Throwable
"failureMessage :: STRING?)", "failureMessage :: STRING?)",
"List all indexes in the database.", "READ" ), "List all indexes in the database.", "READ" ),
proc( "db.awaitIndex", "(index :: STRING?, timeOutSeconds = 300 :: INTEGER?) :: VOID", proc( "db.awaitIndex", "(index :: STRING?, timeOutSeconds = 300 :: INTEGER?) :: VOID",
"Wait for an index to come online (for example: CALL db.awaitIndex(\":Person(name)\"), " + "Wait for an index to come online (for example: CALL db.awaitIndex(\":Person(name)\")).", "READ" ),
"or CALL db.awaitIndex(\"index_name\")).", "READ" ),
proc( "db.awaitIndexes", "(timeOutSeconds = 300 :: INTEGER?) :: VOID", proc( "db.awaitIndexes", "(timeOutSeconds = 300 :: INTEGER?) :: VOID",
"Wait for all indexes to come online (for example: CALL db.awaitIndexes(\"500\")).", "READ" ), "Wait for all indexes to come online (for example: CALL db.awaitIndexes(\"500\")).", "READ" ),
proc( "db.resampleIndex", "(index :: STRING?) :: VOID", proc( "db.resampleIndex", "(index :: STRING?) :: VOID",
"Schedule resampling of an index (for example: CALL db.resampleIndex(\":Person(name)\"), " + "Schedule resampling of an index (for example: CALL db.resampleIndex(\":Person(name)\")).", "READ" ),
"or CALL db.resampleIndex(\"index_name\")).", "READ" ),
proc( "db.resampleOutdatedIndexes", "() :: VOID", "Schedule resampling of all outdated indexes.", "READ" ), proc( "db.resampleOutdatedIndexes", "() :: VOID", "Schedule resampling of all outdated indexes.", "READ" ),
proc( "db.propertyKeys", "() :: (propertyKey :: STRING?)", "List all property keys in the database.", "READ" ), proc( "db.propertyKeys", "() :: (propertyKey :: STRING?)", "List all property keys in the database.", "READ" ),
proc( "db.labels", "() :: (label :: STRING?)", "List all labels in the database.", "READ" ), proc( "db.labels", "() :: (label :: STRING?)", "List all labels in the database.", "READ" ),
Expand Down Expand Up @@ -229,6 +227,9 @@ public void listProcedures() throws Throwable
"YIELD index, providerName, status", "SCHEMA" ), "YIELD index, providerName, status", "SCHEMA" ),
proc( "db.index.fulltext.awaitEventuallyConsistentIndexRefresh", "() :: VOID", proc( "db.index.fulltext.awaitEventuallyConsistentIndexRefresh", "() :: VOID",
"Wait for the updates from recently committed transactions to be applied to any eventually-consistent fulltext indexes.", "READ" ), "Wait for the updates from recently committed transactions to be applied to any eventually-consistent fulltext indexes.", "READ" ),
proc( "db.index.fulltext.awaitIndex", "(index :: STRING?, timeOutSeconds = 300 :: INTEGER?) :: VOID",
"Similar to db.awaitIndex(index, timeout), except instead of an index pattern, the index is specified by name. " +
"The name can be quoted by backticks, if necessary.", "READ" ),
proc( "db.index.fulltext.createNodeIndex", "(indexName :: STRING?, labels :: LIST? OF STRING?, propertyNames :: LIST? OF STRING?, " + proc( "db.index.fulltext.createNodeIndex", "(indexName :: STRING?, labels :: LIST? OF STRING?, propertyNames :: LIST? OF STRING?, " +
"config = {} :: MAP?) :: VOID", startsWith( "Create a node fulltext index for the given labels and properties." ), "SCHEMA" ), "config = {} :: MAP?) :: VOID", startsWith( "Create a node fulltext index for the given labels and properties." ), "SCHEMA" ),
proc( "db.index.fulltext.createRelationshipIndex", proc( "db.index.fulltext.createRelationshipIndex",
Expand Down
Expand Up @@ -31,6 +31,7 @@
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Stream; import java.util.stream.Stream;


import org.neo4j.graphdb.DependencyResolver;
import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException; import org.neo4j.graphdb.NotFoundException;
Expand All @@ -39,11 +40,14 @@
import org.neo4j.graphdb.schema.Schema; import org.neo4j.graphdb.schema.Schema;
import org.neo4j.internal.kernel.api.IndexReference; import org.neo4j.internal.kernel.api.IndexReference;
import org.neo4j.internal.kernel.api.exceptions.InvalidTransactionTypeKernelException; import org.neo4j.internal.kernel.api.exceptions.InvalidTransactionTypeKernelException;
import org.neo4j.internal.kernel.api.exceptions.ProcedureException;
import org.neo4j.internal.kernel.api.exceptions.schema.IndexNotFoundKernelException; import org.neo4j.internal.kernel.api.exceptions.schema.IndexNotFoundKernelException;
import org.neo4j.internal.kernel.api.exceptions.schema.SchemaKernelException; import org.neo4j.internal.kernel.api.exceptions.schema.SchemaKernelException;
import org.neo4j.internal.kernel.api.schema.SchemaDescriptor; import org.neo4j.internal.kernel.api.schema.SchemaDescriptor;
import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.builtinprocs.IndexProcedures;
import org.neo4j.kernel.impl.api.KernelTransactionImplementation; import org.neo4j.kernel.impl.api.KernelTransactionImplementation;
import org.neo4j.kernel.impl.api.index.IndexingService;
import org.neo4j.procedure.Context; import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description; import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name; import org.neo4j.procedure.Name;
Expand Down Expand Up @@ -73,6 +77,9 @@ public class FulltextProcedures
@Context @Context
public GraphDatabaseService db; public GraphDatabaseService db;


@Context
public DependencyResolver resolver;

@Context @Context
public FulltextAdapter accessor; public FulltextAdapter accessor;


Expand All @@ -90,6 +97,22 @@ public void awaitRefresh()
accessor.awaitRefresh(); accessor.awaitRefresh();
} }


@Description( "Similar to db.awaitIndex(index, timeout), except instead of an index pattern, the index is specified by name. " +
"The name can be quoted by backticks, if necessary." )
@Procedure( name = "db.index.fulltext.awaitIndex", mode = READ )
public void awaitIndex( @Name( "index" ) String index, @Name( value = "timeOutSeconds", defaultValue = "300" ) long timeout ) throws ProcedureException
{
try ( IndexProcedures indexProcedures = indexProcedures() )
{
indexProcedures.awaitIndexByName( index, timeout, TimeUnit.SECONDS );
}
}

private IndexProcedures indexProcedures()
{
return new IndexProcedures( tx, resolver.resolveDependency( IndexingService.class ) );
}

@Description( "Create a node fulltext index for the given labels and properties. " + @Description( "Create a node fulltext index for the given labels and properties. " +
"The optional 'config' map parameter can be used to supply settings to the index. " + "The optional 'config' map parameter can be used to supply settings to the index. " +
"Note: index specific settings are currently experimental, and might not replicated correctly in a cluster, or during backup. " + "Note: index specific settings are currently experimental, and might not replicated correctly in a cluster, or during backup. " +
Expand Down
Expand Up @@ -169,15 +169,15 @@ public Stream<IndexResult> listIndexes() throws ProcedureException
} }
} }


@Description( "Wait for an index to come online (for example: CALL db.awaitIndex(\":Person(name)\"), or CALL db.awaitIndex(\"index_name\"))." ) @Description( "Wait for an index to come online (for example: CALL db.awaitIndex(\":Person(name)\"))." )
@Procedure( name = "db.awaitIndex", mode = READ ) @Procedure( name = "db.awaitIndex", mode = READ )
public void awaitIndex( @Name( "index" ) String index, public void awaitIndex( @Name( "index" ) String index,
@Name( value = "timeOutSeconds", defaultValue = "300" ) long timeout ) @Name( value = "timeOutSeconds", defaultValue = "300" ) long timeout )
throws ProcedureException throws ProcedureException
{ {
try ( IndexProcedures indexProcedures = indexProcedures() ) try ( IndexProcedures indexProcedures = indexProcedures() )
{ {
indexProcedures.awaitIndex( index, timeout, TimeUnit.SECONDS ); indexProcedures.awaitIndexByPattern( index, timeout, TimeUnit.SECONDS );
} }
} }


Expand All @@ -188,7 +188,7 @@ public void awaitIndexes( @Name( value = "timeOutSeconds", defaultValue = "300"
graphDatabaseAPI.schema().awaitIndexesOnline( timeout, TimeUnit.SECONDS ); graphDatabaseAPI.schema().awaitIndexesOnline( timeout, TimeUnit.SECONDS );
} }


@Description( "Schedule resampling of an index (for example: CALL db.resampleIndex(\":Person(name)\"), or CALL db.resampleIndex(\"index_name\"))." ) @Description( "Schedule resampling of an index (for example: CALL db.resampleIndex(\":Person(name)\"))." )
@Procedure( name = "db.resampleIndex", mode = READ ) @Procedure( name = "db.resampleIndex", mode = READ )
public void resampleIndex( @Name( "index" ) String index ) throws ProcedureException public void resampleIndex( @Name( "index" ) String index ) throws ProcedureException
{ {
Expand Down
Expand Up @@ -57,16 +57,23 @@ public IndexProcedures( KernelTransaction tx, IndexingService indexingService )
this.indexingService = indexingService; this.indexingService = indexingService;
} }


public void awaitIndex( String indexSpecification, long timeout, TimeUnit timeoutUnits ) public void awaitIndexByPattern( String indexPattern, long timeout, TimeUnit timeoutUnits )
throws ProcedureException throws ProcedureException
{ {
IndexSpecifier specifier = IndexSpecifier.patternOrName( indexSpecification ); IndexSpecifier specifier = IndexSpecifier.byPattern( indexPattern );
waitUntilOnline( getIndex( specifier ), specifier, timeout, timeoutUnits );
}

public void awaitIndexByName( String indexName, long timeout, TimeUnit timeoutUnits )
throws ProcedureException
{
IndexSpecifier specifier = IndexSpecifier.byName( indexName );
waitUntilOnline( getIndex( specifier ), specifier, timeout, timeoutUnits ); waitUntilOnline( getIndex( specifier ), specifier, timeout, timeoutUnits );
} }


public void resampleIndex( String indexSpecification ) throws ProcedureException public void resampleIndex( String indexSpecification ) throws ProcedureException
{ {
IndexSpecifier specifier = IndexSpecifier.patternOrName( indexSpecification ); IndexSpecifier specifier = IndexSpecifier.byPattern( indexSpecification );
try try
{ {
triggerSampling( getIndex( specifier ) ); triggerSampling( getIndex( specifier ) );
Expand Down Expand Up @@ -102,7 +109,7 @@ private Stream<BuiltInProcedures.SchemaIndexInfo> createIndex( String indexSpeci
IndexCreator indexCreator ) throws ProcedureException IndexCreator indexCreator ) throws ProcedureException
{ {
assertProviderNameNotNull( providerName ); assertProviderNameNotNull( providerName );
IndexSpecifier index = IndexSpecifier.pattern( indexSpecification ); IndexSpecifier index = IndexSpecifier.byPattern( indexSpecification );
int labelId = getOrCreateLabelId( index.label() ); int labelId = getOrCreateLabelId( index.label() );
int[] propertyKeyIds = getOrCreatePropertyIds( index.properties() ); int[] propertyKeyIds = getOrCreatePropertyIds( index.properties() );
try try
Expand Down
Expand Up @@ -59,17 +59,22 @@ public class IndexSpecifier
private final String[] properties; private final String[] properties;
private final String name; private final String name;


public static IndexSpecifier patternOrName( String specification ) public static IndexSpecifier byPatternOrName( String specification )
{ {
return parse( specification, true ); return parse( specification, true, true );
} }


public static IndexSpecifier pattern( String specification ) public static IndexSpecifier byPattern( String specification )
{ {
return parse( specification, false ); return parse( specification, false, true );
} }


private static IndexSpecifier parse( String specification, boolean allowIndexNameSpecs ) public static IndexSpecifier byName( String specification )
{
return parse( specification, true, false );
}

private static IndexSpecifier parse( String specification, boolean allowIndexNameSpecs, boolean allowIndexPatternSpecs )
{ {
Matcher matcher = PATTERN_START_INDEX_NAME_OR_LABEL.matcher( specification ); Matcher matcher = PATTERN_START_INDEX_NAME_OR_LABEL.matcher( specification );
if ( !matcher.find() ) if ( !matcher.find() )
Expand All @@ -93,6 +98,12 @@ private static IndexSpecifier parse( String specification, boolean allowIndexNam
throw new IllegalArgumentException( "Invalid characters following index name: '" + specification + "'" ); throw new IllegalArgumentException( "Invalid characters following index name: '" + specification + "'" );
} }


if ( !allowIndexPatternSpecs )
{
throw new IllegalArgumentException( "Cannot parse index specification: '" + specification +
"' - it looks like an index pattern, but an index name was expected." );
}

String label = either( matcher.group( GROUP_LABEL ), matcher.group( GROUP_QUOTED_LABEL ) ); String label = either( matcher.group( GROUP_LABEL ), matcher.group( GROUP_QUOTED_LABEL ) );
if ( label == null ) if ( label == null )
{ {
Expand Down
Expand Up @@ -89,7 +89,7 @@ public void shouldThrowAnExceptionIfTheLabelDoesntExist()


try try
{ {
procedure.awaitIndex( ":NonExistentLabel(prop)", TIMEOUT, TIME_UNIT ); procedure.awaitIndexByPattern( ":NonExistentLabel(prop)", TIMEOUT, TIME_UNIT );
fail( "Expected an exception" ); fail( "Expected an exception" );
} }
catch ( ProcedureException e ) catch ( ProcedureException e )
Expand All @@ -105,7 +105,7 @@ public void shouldThrowAnExceptionIfThePropertyKeyDoesntExist()


try try
{ {
procedure.awaitIndex( ":Label(nonExistentProperty)", TIMEOUT, TIME_UNIT ); procedure.awaitIndexByPattern( ":Label(nonExistentProperty)", TIMEOUT, TIME_UNIT );
fail( "Expected an exception" ); fail( "Expected an exception" );
} }
catch ( ProcedureException e ) catch ( ProcedureException e )
Expand All @@ -122,7 +122,7 @@ public void shouldLookUpTheIndexByLabelIdAndPropertyKeyId() throws ProcedureExce
when( schemaRead.index( anyInt(), any() ) ).thenReturn( anyIndex ); when( schemaRead.index( anyInt(), any() ) ).thenReturn( anyIndex );
when( schemaRead.indexGetState( any( IndexReference.class ) ) ).thenReturn( ONLINE ); when( schemaRead.indexGetState( any( IndexReference.class ) ) ).thenReturn( ONLINE );


procedure.awaitIndex( ":Person(name)", TIMEOUT, TIME_UNIT ); procedure.awaitIndexByPattern( ":Person(name)", TIMEOUT, TIME_UNIT );


verify( schemaRead ).index( descriptor.getLabelId(), descriptor.getPropertyId() ); verify( schemaRead ).index( descriptor.getLabelId(), descriptor.getPropertyId() );
} }
Expand All @@ -135,7 +135,7 @@ public void shouldLookUpTheIndexByIndexName() throws ProcedureException, IndexNo
when( schemaRead.indexGetForName( "my index" ) ).thenReturn( anyIndex ); when( schemaRead.indexGetForName( "my index" ) ).thenReturn( anyIndex );
when( schemaRead.indexGetState( any( IndexReference.class ) ) ).thenReturn( ONLINE ); when( schemaRead.indexGetState( any( IndexReference.class ) ) ).thenReturn( ONLINE );


procedure.awaitIndex( "`my index`", TIMEOUT, TIME_UNIT ); procedure.awaitIndexByName( "`my index`", TIMEOUT, TIME_UNIT );


verify( schemaRead ).indexGetForName( "my index" ); verify( schemaRead ).indexGetForName( "my index" );
} }
Expand All @@ -151,7 +151,7 @@ public void shouldThrowAnExceptionIfTheIndexHasFailed() throws IndexNotFoundKern


try try
{ {
procedure.awaitIndex( ":Person(name)", TIMEOUT, TIME_UNIT ); procedure.awaitIndexByPattern( ":Person(name)", TIMEOUT, TIME_UNIT );
fail( "Expected an exception" ); fail( "Expected an exception" );
} }
catch ( ProcedureException e ) catch ( ProcedureException e )
Expand All @@ -170,7 +170,7 @@ public void shouldThrowAnExceptionIfTheIndexDoesNotExist()


try try
{ {
procedure.awaitIndex( ":Person(name)", TIMEOUT, TIME_UNIT ); procedure.awaitIndexByPattern( ":Person(name)", TIMEOUT, TIME_UNIT );
fail( "Expected an exception" ); fail( "Expected an exception" );
} }
catch ( ProcedureException e ) catch ( ProcedureException e )
Expand All @@ -179,6 +179,12 @@ public void shouldThrowAnExceptionIfTheIndexDoesNotExist()
} }
} }


@Test( expected = IllegalArgumentException.class )
public void shouldThrowAnExceptionIfGivenAnIndexName() throws ProcedureException
{
procedure.awaitIndexByPattern( "`some index`", TIMEOUT, TIME_UNIT );
}

@Test @Test
public void shouldThrowAnExceptionIfTheIndexWithGivenNameDoesNotExist() public void shouldThrowAnExceptionIfTheIndexWithGivenNameDoesNotExist()
{ {
Expand All @@ -188,7 +194,7 @@ public void shouldThrowAnExceptionIfTheIndexWithGivenNameDoesNotExist()


try try
{ {
procedure.awaitIndex( "`some index`", TIMEOUT, TIME_UNIT ); procedure.awaitIndexByName( "`some index`", TIMEOUT, TIME_UNIT );
fail( "Expected an exception" ); fail( "Expected an exception" );
} }
catch ( ProcedureException e ) catch ( ProcedureException e )
Expand All @@ -212,7 +218,7 @@ public void shouldBlockUntilTheIndexIsOnline() throws IndexNotFoundKernelExcepti
{ {
try try
{ {
procedure.awaitIndex( ":Person(name)", TIMEOUT, TIME_UNIT ); procedure.awaitIndexByPattern( ":Person(name)", TIMEOUT, TIME_UNIT );
} }
catch ( ProcedureException e ) catch ( ProcedureException e )
{ {
Expand Down Expand Up @@ -242,7 +248,7 @@ public void shouldTimeoutIfTheIndexTakesTooLongToComeOnline() throws Interrupted
try try
{ {
// We wait here, because we expect timeout // We wait here, because we expect timeout
procedure.awaitIndex( ":Person(name)", 0, TIME_UNIT ); procedure.awaitIndexByPattern( ":Person(name)", 0, TIME_UNIT );
} }
catch ( ProcedureException e ) catch ( ProcedureException e )
{ {
Expand Down
Expand Up @@ -265,8 +265,7 @@ public void shouldListCorrectBuiltinProcedures() throws Throwable
"dbms.listConfig(searchString = :: STRING?) :: (name :: STRING?, description :: STRING?, value :: STRING?, dynamic :: BOOLEAN?)", "dbms.listConfig(searchString = :: STRING?) :: (name :: STRING?, description :: STRING?, value :: STRING?, dynamic :: BOOLEAN?)",
"List the currently active config of Neo4j.", "DBMS" ), "List the currently active config of Neo4j.", "DBMS" ),
record( "db.awaitIndex", "db.awaitIndex(index :: STRING?, timeOutSeconds = 300 :: INTEGER?) :: VOID", record( "db.awaitIndex", "db.awaitIndex(index :: STRING?, timeOutSeconds = 300 :: INTEGER?) :: VOID",
"Wait for an index to come online (for example: CALL db.awaitIndex(\":Person(name)\"), " + "Wait for an index to come online (for example: CALL db.awaitIndex(\":Person(name)\")).", "READ" ),
"or CALL db.awaitIndex(\"index_name\")).", "READ" ),
record( "db.awaitIndexes", "db.awaitIndexes(timeOutSeconds = 300 :: INTEGER?) :: VOID", record( "db.awaitIndexes", "db.awaitIndexes(timeOutSeconds = 300 :: INTEGER?) :: VOID",
"Wait for all indexes to come online (for example: CALL db.awaitIndexes(\"500\")).", "READ" ), "Wait for all indexes to come online (for example: CALL db.awaitIndexes(\"500\")).", "READ" ),
record( "db.constraints", "db.constraints() :: (description :: STRING?)", record( "db.constraints", "db.constraints() :: (description :: STRING?)",
Expand All @@ -281,8 +280,7 @@ public void shouldListCorrectBuiltinProcedures() throws Throwable
record( "db.relationshipTypes", "db.relationshipTypes() :: (relationshipType :: STRING?)", record( "db.relationshipTypes", "db.relationshipTypes() :: (relationshipType :: STRING?)",
"List all relationship types in the database.", "READ" ), "List all relationship types in the database.", "READ" ),
record( "db.resampleIndex", "db.resampleIndex(index :: STRING?) :: VOID", record( "db.resampleIndex", "db.resampleIndex(index :: STRING?) :: VOID",
"Schedule resampling of an index (for example: CALL db.resampleIndex(\":Person(name)\"), " + "Schedule resampling of an index (for example: CALL db.resampleIndex(\":Person(name)\")).", "READ" ),
"or CALL db.resampleIndex(\"index_name\")).", "READ" ),
record( "db.resampleOutdatedIndexes", "db.resampleOutdatedIndexes() :: VOID", record( "db.resampleOutdatedIndexes", "db.resampleOutdatedIndexes() :: VOID",
"Schedule resampling of all outdated indexes.", "READ" ), "Schedule resampling of all outdated indexes.", "READ" ),
record( "db.schema", record( "db.schema",
Expand Down

0 comments on commit 96ca2f8

Please sign in to comment.