Skip to content

Commit

Permalink
BuiltInProcedures for index creation
Browse files Browse the repository at this point in the history
BuiltInProcedures
- db.createIndex
EnterpriseBuiltInProcedure
- db.createNodeKey
- db.createUniquePropertyConstraint

Procedures allow user to specify index provider upon index
and constraint creation. This is otherwise controlled by
dbms.index.default_schema_provider and to change this you
need to restart db.

Like other procedures interacting with schema indexes it takes
schema pattern ":Label( prop1, prop2,... )" as argument as well as
index provider name.
Allowed index provider names correspond to IndexProvider.Descriptor.name()
and allowed values for setting (dbms.index.default_schema_provider).

If label or property tokens are missing, they will be created.

* Why do we need this? *
We need a way to mitigate for the limitations in native string index
(4k length and limited support for contains and ends with).
When creating an index (or constraint) through Cypher the default
index provider is always used. The default provider is controlled with
db.index.default_schema_provider setting. To change this setting user
have to restart the db. This is sad.
Those procedures allow user to specify index provider per index creation
without having to do any db restart.
This gives user two different ways to create an index which is bad and
optimal way would be to have support for this in cypher language. For
3.4 release, that is not feasible and so falling back to procedures is
the way to go.
  • Loading branch information
burqen authored and MishaDemianenko committed Jun 9, 2018
1 parent 0042b44 commit c735e50
Show file tree
Hide file tree
Showing 16 changed files with 757 additions and 16 deletions.
Expand Up @@ -61,6 +61,11 @@ public static AnonymousContext writeOnly()
return new AnonymousContext( AccessMode.Static.WRITE_ONLY );
}

public static AnonymousContext full()
{
return new AnonymousContext( AccessMode.Static.FULL );
}

@Override
public AuthSubject subject()
{
Expand Down
Expand Up @@ -68,6 +68,7 @@

import static org.neo4j.helpers.collection.Iterators.asList;
import static org.neo4j.procedure.Mode.READ;
import static org.neo4j.procedure.Mode.SCHEMA;
import static org.neo4j.procedure.Mode.WRITE;

@SuppressWarnings( {"unused", "WeakerAccess"} )
Expand Down Expand Up @@ -221,6 +222,20 @@ public Stream<ConstraintResult> listConstraints()
.map( ConstraintResult::new );
}

@Description( "Create a schema index with specified index provider (for example: CALL db.createIndex(\":Person(name)\", \"lucene+native-2.0\")) - " +
"YIELD index, providerName, status" )
@Procedure( name = "db.createIndex", mode = SCHEMA )
public Stream<SchemaIndexInfo> createIndex(
@Name( "index" ) String index,
@Name( "providerName" ) String providerName )
throws ProcedureException
{
try ( IndexProcedures indexProcedures = indexProcedures() )
{
return indexProcedures.createIndex( index, providerName );
}
}

@Description( "Get node from explicit index. Replaces `START n=node:nodes(key = 'A')`" )
@Procedure( name = "db.index.explicit.seekNodes", mode = READ )
public Stream<NodeResult> nodeManualIndexSeek( @Name( "indexName" ) String explicitIndexName,
Expand Down Expand Up @@ -827,6 +842,20 @@ private IndexResult( String description, String label, List<String> properties,
}
}

public static class SchemaIndexInfo
{
public final String index;
public final String providerName;
public final String status;

public SchemaIndexInfo( String index, String providerName, String status )
{
this.index = index;
this.providerName = providerName;
this.status = status;
}
}

public static class ExplicitIndexInfo
{
public final String type;
Expand Down
Expand Up @@ -21,17 +21,24 @@

import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;

import org.neo4j.function.Predicates;
import org.neo4j.internal.kernel.api.CapableIndexReference;
import org.neo4j.internal.kernel.api.IndexReference;
import org.neo4j.internal.kernel.api.InternalIndexState;
import org.neo4j.internal.kernel.api.SchemaWrite;
import org.neo4j.internal.kernel.api.TokenRead;
import org.neo4j.internal.kernel.api.exceptions.InvalidTransactionTypeKernelException;
import org.neo4j.internal.kernel.api.exceptions.ProcedureException;
import org.neo4j.internal.kernel.api.exceptions.schema.IllegalTokenNameException;
import org.neo4j.internal.kernel.api.exceptions.schema.SchemaKernelException;
import org.neo4j.internal.kernel.api.exceptions.schema.TooManyLabelsException;
import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.api.Statement;
import org.neo4j.kernel.api.exceptions.Status;
import org.neo4j.kernel.api.exceptions.index.IndexNotFoundKernelException;
import org.neo4j.kernel.api.schema.LabelSchemaDescriptor;
import org.neo4j.kernel.api.schema.SchemaDescriptorFactory;
import org.neo4j.kernel.impl.api.index.IndexingService;
import org.neo4j.kernel.impl.api.index.sampling.IndexSamplingMode;
Expand Down Expand Up @@ -78,6 +85,55 @@ public void resampleOutdatedIndexes()
indexingService.triggerIndexSampling( IndexSamplingMode.TRIGGER_REBUILD_UPDATED );
}

public Stream<BuiltInProcedures.SchemaIndexInfo> createIndex( String indexSpecification, String providerName ) throws ProcedureException
{
String statusMessage = "index created";
return createIndex( indexSpecification, providerName, statusMessage, SchemaWrite::indexCreate );
}

public Stream<BuiltInProcedures.SchemaIndexInfo> createUniquePropertyConstraint( String indexSpecification, String providerName ) throws ProcedureException
{
return createIndex( indexSpecification, providerName, "uniqueness constraint online", SchemaWrite::uniquePropertyConstraintCreate );
}

public Stream<BuiltInProcedures.SchemaIndexInfo> createNodeKey( String indexSpecification, String providerName ) throws ProcedureException
{
return createIndex( indexSpecification, providerName, "node key constraint online", SchemaWrite::nodeKeyConstraintCreate );
}

private Stream<BuiltInProcedures.SchemaIndexInfo> createIndex( String indexSpecification, String providerName, String statusMessage,
IndexCreator indexCreator ) throws ProcedureException
{
assertProviderNameNotNull( providerName );
IndexSpecifier index = parse( indexSpecification );
int labelId = getOrCreateLabelId( index.label() );
int[] propertyKeyIds = getOrCreatePropertyIds( index.properties() );
try
{
SchemaWrite schemaWrite = ktx.schemaWrite();
LabelSchemaDescriptor labelSchemaDescriptor = SchemaDescriptorFactory.forLabel( labelId, propertyKeyIds );
indexCreator.create( schemaWrite, labelSchemaDescriptor, providerName );
return Stream.of( new BuiltInProcedures.SchemaIndexInfo( indexSpecification, providerName, statusMessage ) );
}
catch ( InvalidTransactionTypeKernelException | SchemaKernelException e )
{
throw new ProcedureException( e.status(), e, "" );
}
}

private void assertProviderNameNotNull( String providerName ) throws ProcedureException
{
if ( providerName == null )
{
throw new ProcedureException( Status.Procedure.ProcedureCallFailed, indexProviderNullMessage() );
}
}

private String indexProviderNullMessage()
{
return "Could not create index with specified index provider being null.";
}

private IndexSpecifier parse( String specification )
{
return new IndexSpecifier( specification );
Expand All @@ -102,14 +158,42 @@ private int[] getPropertyIds( String[] propertyKeyNames ) throws ProcedureExcept
int propertyKeyId = ktx.tokenRead().propertyKey( propertyKeyNames[i] );
if ( propertyKeyId == TokenRead.NO_TOKEN )
{
throw new ProcedureException( Status.Schema.PropertyKeyAccessFailed, "No such property key %s",
propertyKeyNames );
throw new ProcedureException( Status.Schema.PropertyKeyAccessFailed, "No such property key %s", propertyKeyNames[i] );
}
propertyKeyIds[i] = propertyKeyId;
}
return propertyKeyIds;
}

private int getOrCreateLabelId( String labelName ) throws ProcedureException
{
try
{
return ktx.tokenWrite().labelGetOrCreateForName( labelName );
}
catch ( TooManyLabelsException | IllegalTokenNameException e )
{
throw new ProcedureException( e.status(), e, "" );
}
}

private int[] getOrCreatePropertyIds( String[] propertyKeyNames ) throws ProcedureException
{
int[] propertyKeyIds = new int[propertyKeyNames.length];
for ( int i = 0; i < propertyKeyIds.length; i++ )
{
try
{
propertyKeyIds[i] = ktx.tokenWrite().propertyKeyGetOrCreateForName( propertyKeyNames[i] );
}
catch ( IllegalTokenNameException e )
{
throw new ProcedureException( e.status(), e, "" );
}
}
return propertyKeyIds;
}

private CapableIndexReference getIndex( int labelId, int[] propertyKeyIds, IndexSpecifier index ) throws
ProcedureException
{
Expand Down Expand Up @@ -177,4 +261,10 @@ public void close()
{
statement.close();
}

@FunctionalInterface
private interface IndexCreator
{
void create( SchemaWrite schemaWrite, LabelSchemaDescriptor descriptor, String providerName ) throws SchemaKernelException;
}
}
Expand Up @@ -19,6 +19,8 @@
*/
package org.neo4j.kernel.impl.transaction.state;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -98,13 +100,26 @@ public IndexProvider lookup( String providerDescriptorName ) throws IndexProvide

private IllegalArgumentException notFound( Object key )
{
return new IllegalArgumentException( "Tried to get index provider for an existing index with provider " + key +
" whereas available providers in this session being " + indexProviders + ", and default being " + defaultIndexProvider );
return new IllegalArgumentException( "Tried to get index provider with name " + key +
" whereas available providers in this session being " + Arrays.toString( indexProviderNames() ) + ", and default being " +
defaultIndexProvider.getProviderDescriptor().name() );
}

@Override
public void accept( Consumer<IndexProvider> visitor )
{
indexProviders.values().forEach( visitor );
}

private String[] indexProviderNames()
{
Collection<IndexProvider> providerList = indexProviders.values();
String[] providerNames = new String[providerList.size()];
int index = 0;
for ( IndexProvider indexProvider : providerList )
{
providerNames[index++] = indexProvider.getProviderDescriptor().name();
}
return providerNames;
}
}
Expand Up @@ -323,7 +323,11 @@ public void shouldListCorrectBuiltinProcedures() throws Throwable
"Query JMX management data by domain and name. For instance, \"org.neo4j:*\"", "READ" ),
record( "dbms.clearQueryCaches",
"dbms.clearQueryCaches() :: (value :: STRING?)",
"Clears all query caches.", "DBMS" )
"Clears all query caches.", "DBMS" ),
record( "db.createIndex",
"db.createIndex(index :: STRING?, providerName :: STRING?) :: (index :: STRING?, providerName :: STRING?, status :: STRING?)",
"Create a schema index with specified index provider (for example: CALL db.createIndex(\":Person(name)\", lucene+native-2.0 )) - " +
"YIELD index, providerName, status", "WRITE" )
) );
}

Expand Down Expand Up @@ -383,7 +387,7 @@ public void shouldCloseStatementIfExceptionIsThrownDbPropertyKeys()
}

@Test
public void shouldCloseStatementIfExceptionIsThrownDRelationshipTypes()
public void shouldCloseStatementIfExceptionIsThrownDbRelationshipTypes()
{
// Given
RuntimeException runtimeException = new RuntimeException();
Expand Down
Expand Up @@ -109,6 +109,13 @@ protected Procedures procs() throws TransactionFailureException
return transaction.procedures();
}

protected Procedures procsSchema() throws TransactionFailureException
{
session = kernel.beginSession( AnonymousContext.full() );
transaction = session.beginTransaction( KernelTransaction.Type.implicit );
return transaction.procedures();
}

protected Transaction newTransaction() throws TransactionFailureException
{
session = kernel.beginSession( AnonymousContext.read() );
Expand Down
Expand Up @@ -22,9 +22,8 @@
import org.junit.Test;

import org.neo4j.kernel.api.index.IndexProvider;
import org.neo4j.kernel.impl.api.index.IndexProviderMap;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand All @@ -44,7 +43,7 @@ public void shouldNotSupportMultipleProvidersWithSameDescriptor()
// when
try
{
new DefaultIndexProviderMap( provider1, asList( provider2 ) );
new DefaultIndexProviderMap( provider1, singletonList( provider2 ) );
fail( "Should have failed" );
}
catch ( IllegalArgumentException e )
Expand All @@ -61,7 +60,6 @@ public void shouldThrowOnLookupOnUnknownProvider()
when( provider.getProviderDescriptor() ).thenReturn( new IndexProvider.Descriptor( "provider", "1.2" ) );

// when
IndexProviderMap map = new DefaultIndexProviderMap( provider );
try
{
new DefaultIndexProviderMap( provider ).lookup( new IndexProvider.Descriptor( "provider2", "1.2" ) );
Expand Down

0 comments on commit c735e50

Please sign in to comment.