From c257843fe8fc4e5a78f4d666765ccfc718311820 Mon Sep 17 00:00:00 2001 From: Pontus Melke Date: Thu, 17 Mar 2016 16:48:31 +0100 Subject: [PATCH] Include unique indexes in procedure --- ...tandaloneProcedureCallAcceptanceTest.scala | 6 +-- .../builtinprocs/ListIndexesProcedure.java | 15 +++++- .../builtinprocs/BuiltInProceduresTest.java | 30 +++++++++-- .../integrationtest/BuiltinProceduresIT.java | 50 ++++++++++++++++++- 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/community/cypher/acceptance/src/test/scala/org/neo4j/internal/cypher/acceptance/StandaloneProcedureCallAcceptanceTest.scala b/community/cypher/acceptance/src/test/scala/org/neo4j/internal/cypher/acceptance/StandaloneProcedureCallAcceptanceTest.scala index 2d0bd3d60b182..9fa62d50b47cb 100644 --- a/community/cypher/acceptance/src/test/scala/org/neo4j/internal/cypher/acceptance/StandaloneProcedureCallAcceptanceTest.scala +++ b/community/cypher/acceptance/src/test/scala/org/neo4j/internal/cypher/acceptance/StandaloneProcedureCallAcceptanceTest.scala @@ -284,7 +284,7 @@ class StandaloneProcedureCallAcceptanceTest extends ProcedureCallAcceptanceTest registerDummyInOutProcedure(Neo4jTypes.NTString, Neo4jTypes.NTNumber) // Then - an [SyntaxException] shouldBe thrownBy(execute("CALL my.first.proc('ten')")) + a [SyntaxException] shouldBe thrownBy(execute("CALL my.first.proc('ten')")) } test("should fail if too many arguments") { @@ -292,7 +292,7 @@ class StandaloneProcedureCallAcceptanceTest extends ProcedureCallAcceptanceTest registerDummyInOutProcedure(Neo4jTypes.NTString, Neo4jTypes.NTNumber) // Then - an [SyntaxException] shouldBe thrownBy(execute("CALL my.first.proc('ten', 10, 42)")) + a [SyntaxException] shouldBe thrownBy(execute("CALL my.first.proc('ten', 10, 42)")) } test("should fail if implicit argument is missing") { @@ -327,6 +327,6 @@ class StandaloneProcedureCallAcceptanceTest extends ProcedureCallAcceptanceTest // Then result.toList should equal( - List(Map("description" -> "INDEX ON :A(prop)", "state" -> "ONLINE"))) + List(Map("description" -> "INDEX ON :A(prop)", "state" -> "ONLINE", "unique" -> false))) } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/ListIndexesProcedure.java b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/ListIndexesProcedure.java index 8e8c59d6ba5db..42aa02419c3a0 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/ListIndexesProcedure.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/ListIndexesProcedure.java @@ -20,6 +20,8 @@ package org.neo4j.kernel.builtinprocs; import java.util.List; +import java.util.Set; +import java.util.function.Function; import org.neo4j.collection.RawIterator; import org.neo4j.kernel.api.Statement; @@ -35,6 +37,7 @@ import static org.neo4j.helpers.collection.Iterators.asList; import static org.neo4j.helpers.collection.Iterators.asRawIterator; +import static org.neo4j.helpers.collection.Iterators.asSet; import static org.neo4j.helpers.collection.Iterators.map; import static org.neo4j.kernel.api.proc.CallableProcedure.Context.KERNEL_TRANSACTION; import static org.neo4j.kernel.api.proc.ProcedureSignature.procedureSignature; @@ -46,6 +49,7 @@ protected ListIndexesProcedure(ProcedureName procedureName) super( procedureSignature( procedureName ) .out( "description", Neo4jTypes.NTString ) .out( "state", Neo4jTypes.NTString ) + .out( "unique", Neo4jTypes.NTBoolean ) .build() ); } @@ -58,13 +62,22 @@ public RawIterator apply( Context ctx, Object[] inp List indexes = asList( statement.readOperations().indexesGetAll() ); + + Set uniqueIndexes = asSet( statement.readOperations().uniqueIndexesGetAll() ); + indexes.addAll( uniqueIndexes ); indexes.sort( (a,b) -> a.userDescription(tokens).compareTo( b.userDescription(tokens) ) ); + return format( indexes, statement, tokens, uniqueIndexes::contains ); + } + + private RawIterator format(List indexes, + Statement statement, TokenNameLookup tokens, Function unqiue) + { return map( ( index ) -> { try { return new Object[]{"INDEX ON " + index.userDescription( tokens ), - statement.readOperations().indexGetState( index ).toString()}; + statement.readOperations().indexGetState( index ).toString(), unqiue.apply( index )}; } catch ( IndexNotFoundKernelException e ) { diff --git a/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInProceduresTest.java b/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInProceduresTest.java index 60f3d31c1375c..a9077ef8b3784 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInProceduresTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInProceduresTest.java @@ -61,6 +61,7 @@ public class BuiltInProceduresTest { private final List indexes = new LinkedList<>(); + private final List uniqueIndexes = new LinkedList<>(); private final List constraints = new LinkedList<>(); private final Map labels = new HashMap<>(); private final Map propKeys = new HashMap<>(); @@ -80,8 +81,21 @@ public void shouldListAllIndexes() throws Throwable givenIndex( "User", "name" ); // When/Then - assertThat( call("db.indexes"), - contains( record( "INDEX ON :User(name)", "ONLINE" ) ) ); + + List call = call( "db.indexes" ); + assertThat( call, + contains( record( "INDEX ON :User(name)", "ONLINE", false ) ) ); + } + + @Test + public void shouldListAllUniqueIndexes() throws Throwable + { + // Given + givenUniqueConstraint( "User", "name" ); + + // When/Then + assertThat( call( "db.indexes" ), + contains( record( "INDEX ON :User(name)", "ONLINE", true ) ) ); } @Test @@ -143,7 +157,7 @@ public void shouldListCorrectBuiltinProcedures() throws Throwable // When/Then assertThat( call( "sys.procedures" ), contains( record( "db.constraints", "db.constraints() :: (description :: STRING?)" ), - record( "db.indexes", "db.indexes() :: (description :: STRING?, state :: STRING?)" ), + record( "db.indexes", "db.indexes() :: (description :: STRING?, state :: STRING?, unique :: BOOLEAN?)" ), record( "db.labels", "db.labels() :: (label :: STRING?)" ), record( "db.propertyKeys", "db.propertyKeys() :: (propertyKey :: STRING?)" ), record( "db.relationshipTypes", "db.relationshipTypes() :: (relationshipType :: STRING?)" ), @@ -175,11 +189,20 @@ private void givenIndex( String label, String propKey ) indexes.add( new IndexDescriptor( labelId, propId ) ); } + private void givenUniqueIndex( String label, String propKey ) + { + int labelId = token( label, labels ); + int propId = token( propKey, propKeys ); + + uniqueIndexes.add( new IndexDescriptor( labelId, propId ) ); + } + private void givenUniqueConstraint( String label, String propKey ) { int labelId = token( label, labels ); int propId = token( propKey, propKeys ); + uniqueIndexes.add( new IndexDescriptor( labelId, propId ) ); constraints.add( new UniquenessConstraint( labelId, propId ) ); } @@ -239,6 +262,7 @@ public void setup() throws Exception when(read.labelsGetAllTokens()).thenAnswer( asTokens(labels) ); when(read.relationshipTypesGetAllTokens()).thenAnswer( asTokens(relTypes) ); when(read.indexesGetAll()).thenAnswer( (i) -> indexes.iterator() ); + when(read.uniqueIndexesGetAll()).thenAnswer( (i) -> uniqueIndexes.iterator() ); when(read.constraintsGetAll()).thenAnswer( (i) -> constraints.iterator() ); when(read.proceduresGetAll() ).thenReturn( procs.getAll() ); diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltinProceduresIT.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltinProceduresIT.java index 0249e42ec30da..bb5c4c6b289cd 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltinProceduresIT.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltinProceduresIT.java @@ -24,12 +24,15 @@ import org.junit.rules.ExpectedException; import org.neo4j.collection.RawIterator; +import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.security.AuthorizationViolationException; import org.neo4j.kernel.api.DataWriteOperations; +import org.neo4j.kernel.api.SchemaWriteOperations; import org.neo4j.kernel.api.exceptions.ProcedureException; import org.neo4j.kernel.api.security.AccessMode; import org.neo4j.server.security.auth.AuthSubject; +import static java.util.concurrent.TimeUnit.SECONDS; import static junit.framework.TestCase.fail; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; @@ -153,7 +156,7 @@ public void listProcedures() throws Throwable // Then assertThat( asList( stream ), containsInAnyOrder( equalTo( new Object[]{"db.constraints", "db.constraints() :: (description :: STRING?)"} ), - equalTo( new Object[]{"db.indexes", "db.indexes() :: (description :: STRING?, state :: STRING?)"} ), + equalTo( new Object[]{"db.indexes", "db.indexes() :: (description :: STRING?, state :: STRING?, unique :: BOOLEAN?)"} ), equalTo( new Object[]{"db.propertyKeys", "db.propertyKeys() :: (propertyKey :: STRING?)"}), equalTo( new Object[]{"db.labels", "db.labels() :: (label :: STRING?)"} ), equalTo( new Object[]{"sys.procedures", "sys.procedures() :: (name :: STRING?, signature :: STRING?)"} ), @@ -220,4 +223,49 @@ public void shouldFailWhenChangePasswordWithStaticAccessModeInDbmsMode() throws assertThat( e.getClass(), equalTo( AuthorizationViolationException.class ) ); } } + + @Test + public void listAllIndexes() throws Throwable + { + // Given + SchemaWriteOperations ops = schemaWriteOperationsInNewTransaction(); + int labelId1 = ops.labelGetOrCreateForName( "Person" ); + int labelId2 = ops.labelGetOrCreateForName( "Age" ); + int propertyKeyId = ops.propertyKeyGetOrCreateForName( "foo" ); + ops.indexCreate( labelId1, propertyKeyId ); + ops.uniquePropertyConstraintCreate( labelId2, propertyKeyId ); + commit(); + + //let indexes come online + try ( Transaction tx = db.beginTx() ) + { + db.schema().awaitIndexOnline( db.schema().getIndexes().iterator().next(), 20, SECONDS ); + tx.success(); + } + + // When + RawIterator stream = + readOperationsInNewTransaction().procedureCallRead( procedureName( "db", "indexes" ), new Object[0] ); + + // Then + assertThat( stream.next(), equalTo( new Object[]{"INDEX ON :Age(foo)", "ONLINE", true} ) ); + assertThat( stream.next(), equalTo( new Object[]{"INDEX ON :Person(foo)", "ONLINE", false} ) ); + } + + @Test + public void shouldFalilistAllIndexesnDbmsMode() throws Throwable + { + try + { + // When + RawIterator stream = dbmsOperations().procedureCallDbms( procedureName( "db", "indexes" ), new Object[0], + AccessMode.Static.NONE ); + fail( "Should have failed." ); + } + catch (Exception e) + { + // Then + assertThat( e.getClass(), equalTo( ProcedureException.class ) ); + } + } }